DevToolBoxFREE
Blog

.htaccess Redirect Cheat Sheet: Copy-Paste Examples

10 min readby DevToolBox

The .htaccess file is one of the most powerful configuration tools for Apache web servers. It lets you control redirects, enforce HTTPS, set security headers, manage caching, and much more -- all without touching the main server configuration. This comprehensive .htaccess redirect cheat sheet provides production-ready, copy-paste examples for every common scenario. Each snippet includes comments explaining exactly what it does.

Basic Redirects

Redirects are the most common use of .htaccess files. Whether you are moving a single page, restructuring an entire directory, or migrating to a new domain, Apache's Redirect and RewriteRule directives make it straightforward. Always use 301 (permanent) redirects for SEO to transfer link equity to the new URL.

Redirect a Single URL

# Redirect a single URL (301 permanent)
Redirect 301 /old-page.html https://example.com/new-page.html

# Using RewriteRule for more control
RewriteEngine On
RewriteRule ^old-page\.html$ /new-page.html [R=301,L]

# Redirect with pattern matching (e.g., old product URLs)
RewriteRule ^products/([0-9]+)\.html$ /shop/item/$1 [R=301,L]

Redirect an Entire Directory

# Redirect entire directory to new location
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1

# Using RewriteRule (preserves subdirectory structure)
RewriteEngine On
RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]

# Redirect directory but keep filenames
RewriteRule ^old-folder/(.+)$ /new-folder/$1 [R=301,L]

Redirect Domain to Domain

# Redirect entire old domain to new domain
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?olddomain\.com$ [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [R=301,L]

# Redirect specific domain alias to primary domain
RewriteEngine On
RewriteCond %{HTTP_HOST} ^olddomain\.net$ [NC,OR]
RewriteCond %{HTTP_HOST} ^olddomain\.org$ [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [R=301,L]

HTTPS Enforcement

Enforcing HTTPS is essential for security and SEO. Google uses HTTPS as a ranking signal, and modern browsers warn users about insecure HTTP connections. These rules use mod_rewrite to redirect all HTTP traffic to HTTPS, with options for handling the www prefix.

HTTP to HTTPS (Without www)

# Force HTTPS (redirect HTTP to HTTPS, non-www)
RewriteEngine On

# Redirect www to non-www
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]

# Redirect HTTP to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]

HTTP to HTTPS (With www)

# Force HTTPS with www prefix
RewriteEngine On

# Redirect non-www to www
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]

# Redirect HTTP to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]

Force Non-www with HTTPS

# Force non-www + HTTPS in a single pass
# Works on shared hosting and most Apache setups
RewriteEngine On

# Handle both www removal and HTTPS enforcement together
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]

RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

# Alternative: Using environment variables (some hosts)
# RewriteCond %{ENV:HTTPS} !on
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

Trailing Slash Handling

Inconsistent trailing slashes create duplicate content issues that hurt SEO. Search engines treat /about and /about/ as different URLs. Pick one style and enforce it consistently across your entire site using these rules.

Add Trailing Slash

# Add trailing slash to all URLs (except files with extensions)
RewriteEngine On

# Only apply to URLs without a file extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !\.[a-zA-Z0-9]{1,5}$
RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]

# Simpler version (may cause issues with some file types)
# RewriteRule ^(.*[^/])$ $1/ [R=301,L]

Remove Trailing Slash

# Remove trailing slash from all URLs (except directories)
RewriteEngine On

# Do not remove trailing slash from actual directories
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [R=301,L]

# Remove trailing slash except for root URL
# RewriteCond %{REQUEST_URI} !^/$
# RewriteRule ^(.*)/$ /$1 [R=301,L]

Query String Handling

Redirecting URLs with query strings requires special attention. By default, Apache appends the original query string to the redirect target. You can preserve, discard, or modify query parameters using the QSA (Query String Append) and ? (discard) flags.

Redirect Preserving Query String

# Redirect preserving the original query string (default behavior)
# /search?q=test -> /find?q=test
RewriteEngine On
RewriteRule ^search$ /find [R=301,L]

# Redirect and APPEND additional query parameters (QSA flag)
# /page?id=5 -> /new-page?id=5&ref=old
RewriteRule ^page$ /new-page?ref=old [R=301,L,QSA]

# Match specific query string and redirect
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^product\.php$ /products/%1? [R=301,L]

Redirect Discarding Query String

# Redirect and DISCARD all query parameters
# /old-page?any=params -> /new-page (clean URL)
RewriteEngine On
RewriteRule ^old-page$ /new-page? [R=301,L]
# The trailing ? strips the query string

# Discard specific query parameters only
# /page?utm_source=x&id=5 -> /page?id=5 (strip tracking params)
RewriteCond %{QUERY_STRING} (^|&)utm_[^&]*
RewriteRule ^(.*)$ /$1? [R=301,L]

Redirect Modifying Query Parameters

# Rewrite query parameter to path segment
# /index.php?page=about -> /about
RewriteEngine On
RewriteCond %{QUERY_STRING} ^page=(.+)$
RewriteRule ^index\.php$ /%1? [R=301,L]

# Rewrite path segment to query parameter
# /category/electronics -> /shop.php?cat=electronics
RewriteRule ^category/([a-zA-Z0-9-]+)$ /shop.php?cat=$1 [L]

# Rename a query parameter
# /search?q=test -> /search?query=test
RewriteCond %{QUERY_STRING} ^q=(.+)$
RewriteRule ^search$ /search?query=%1 [R=301,L]

Custom Error Pages

Custom error pages improve user experience by providing helpful information instead of generic server errors. They also give you the opportunity to guide lost visitors back to useful content. The ErrorDocument directive lets you define custom pages for any HTTP status code.

# Custom error pages
# Place error page files in your document root

# 404 Not Found - page does not exist
ErrorDocument 404 /errors/404.html

# 403 Forbidden - access denied
ErrorDocument 403 /errors/403.html

# 500 Internal Server Error
ErrorDocument 500 /errors/500.html

# 401 Unauthorized - authentication required
ErrorDocument 401 /errors/401.html

# 503 Service Unavailable - maintenance mode
ErrorDocument 503 /errors/maintenance.html

# You can also use inline messages (not recommended for production)
# ErrorDocument 404 "Page not found. Please check the URL."

# Or redirect to an external URL
# ErrorDocument 404 https://example.com/not-found

# ── Maintenance mode (redirect all traffic to maintenance page) ──
# Uncomment during maintenance, recomment when done
# RewriteEngine On
# RewriteCond %{REMOTE_ADDR} !^123\.456\.789\.000$  # Allow your IP
# RewriteCond %{REQUEST_URI} !/errors/maintenance.html$ [NC]
# RewriteCond %{REQUEST_URI} !\.(css|js|png|jpg|gif|ico)$ [NC]
# RewriteRule ^(.*)$ /errors/maintenance.html [R=503,L]

Security Headers via .htaccess

HTTP security headers protect your site from common attacks like clickjacking, cross-site scripting (XSS), and content injection. Adding these headers via .htaccess is the easiest approach when you do not have access to the main Apache configuration. They require mod_headers to be enabled.

# Security headers via .htaccess
# Requires mod_headers to be enabled: a2enmod headers

<IfModule mod_headers.c>

  # X-Frame-Options: Prevent clickjacking by blocking iframes
  # Options: DENY | SAMEORIGIN | ALLOW-FROM uri
  Header always set X-Frame-Options "SAMEORIGIN"

  # X-Content-Type-Options: Prevent MIME-type sniffing
  Header always set X-Content-Type-Options "nosniff"

  # X-XSS-Protection: Enable browser XSS filter (legacy)
  Header always set X-XSS-Protection "1; mode=block"

  # Referrer-Policy: Control referrer information
  Header always set Referrer-Policy "strict-origin-when-cross-origin"

  # Content-Security-Policy: Control resource loading
  # Customize the sources based on your site's needs
  Header always set Content-Security-Policy "default-src 'self'; \
    script-src 'self' 'unsafe-inline' https://cdn.example.com; \
    style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
    img-src 'self' data: https:; \
    font-src 'self' https://fonts.gstatic.com; \
    connect-src 'self' https://api.example.com; \
    frame-ancestors 'self';"

  # Strict-Transport-Security (HSTS): Force HTTPS for 2 years
  # Only add this if your site fully supports HTTPS
  Header always set Strict-Transport-Security \
    "max-age=63072000; includeSubDomains; preload"

  # Permissions-Policy: Disable unused browser features
  Header always set Permissions-Policy \
    "camera=(), microphone=(), geolocation=(), interest-cohort=()"

  # Remove X-Powered-By header (hides PHP/server version)
  Header unset X-Powered-By
  Header always unset X-Powered-By

</IfModule>

# Hide Apache version in server headers
ServerSignature Off

Browser Caching Rules

Browser caching dramatically improves page load times for returning visitors by telling browsers to store static assets locally. The mod_expires module sets Cache-Control and Expires headers automatically. Set long cache times for assets with fingerprinted filenames, and shorter times for HTML.

# Browser caching with mod_expires
# Enable the module: a2enmod expires

<IfModule mod_expires.c>
  ExpiresActive On

  # Default expiration: 1 month
  ExpiresDefault "access plus 1 month"

  # HTML files: short cache (content changes frequently)
  ExpiresByType text/html "access plus 1 hour"

  # CSS and JavaScript: long cache (use fingerprinted filenames)
  ExpiresByType text/css "access plus 1 year"
  ExpiresByType application/javascript "access plus 1 year"
  ExpiresByType text/javascript "access plus 1 year"

  # Images: cache for 1 month
  ExpiresByType image/jpeg "access plus 1 month"
  ExpiresByType image/png "access plus 1 month"
  ExpiresByType image/gif "access plus 1 month"
  ExpiresByType image/webp "access plus 1 month"
  ExpiresByType image/avif "access plus 1 month"
  ExpiresByType image/svg+xml "access plus 1 month"
  ExpiresByType image/x-icon "access plus 1 year"

  # Fonts: cache for 1 year
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType font/woff "access plus 1 year"
  ExpiresByType font/ttf "access plus 1 year"
  ExpiresByType application/font-woff2 "access plus 1 year"
  ExpiresByType application/font-woff "access plus 1 year"

  # JSON/XML data: short cache
  ExpiresByType application/json "access plus 1 hour"
  ExpiresByType application/xml "access plus 1 hour"

  # PDF and documents
  ExpiresByType application/pdf "access plus 1 month"
</IfModule>

# Alternative: Cache-Control headers with mod_headers
<IfModule mod_headers.c>
  # Immutable cache for fingerprinted assets
  <FilesMatch "\.(js|css)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>

  # Short cache for HTML
  <FilesMatch "\.html$">
    Header set Cache-Control "public, max-age=3600, must-revalidate"
  </FilesMatch>

  # No cache for dynamic content
  <FilesMatch "\.(php|cgi)$">
    Header set Cache-Control "no-store, no-cache, must-revalidate"
  </FilesMatch>
</IfModule>

Gzip / Brotli Compression

Compression reduces the size of transferred responses by 60-90%, significantly improving page load times. Apache supports both Gzip (via mod_deflate) and Brotli (via mod_brotli, Apache 2.4.26+). Gzip has universal support, while Brotli achieves better compression ratios for text-based content.

Gzip Compression (mod_deflate)

# Gzip compression using mod_deflate
# Enable the module: a2enmod deflate

<IfModule mod_deflate.c>
  # Compress text-based content types
  AddOutputFilterByType DEFLATE text/plain
  AddOutputFilterByType DEFLATE text/html
  AddOutputFilterByType DEFLATE text/xml
  AddOutputFilterByType DEFLATE text/css
  AddOutputFilterByType DEFLATE text/javascript
  AddOutputFilterByType DEFLATE application/xml
  AddOutputFilterByType DEFLATE application/xhtml+xml
  AddOutputFilterByType DEFLATE application/rss+xml
  AddOutputFilterByType DEFLATE application/atom+xml
  AddOutputFilterByType DEFLATE application/javascript
  AddOutputFilterByType DEFLATE application/x-javascript
  AddOutputFilterByType DEFLATE application/json
  AddOutputFilterByType DEFLATE application/ld+json
  AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
  AddOutputFilterByType DEFLATE font/opentype
  AddOutputFilterByType DEFLATE font/ttf
  AddOutputFilterByType DEFLATE font/woff
  AddOutputFilterByType DEFLATE font/woff2
  AddOutputFilterByType DEFLATE image/svg+xml
  AddOutputFilterByType DEFLATE image/x-icon

  # Do not compress images (already compressed)
  SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif)$ no-gzip

  # Handle browser quirks
  BrowserMatch ^Mozilla/4 gzip-only-text/html
  BrowserMatch ^Mozilla/4\.0[678] no-gzip
  BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

  # Add Vary header for proper caching
  Header append Vary Accept-Encoding
</IfModule>

Brotli Compression (mod_brotli)

# Brotli compression using mod_brotli (Apache 2.4.26+)
# Enable the module: a2enmod brotli

<IfModule mod_brotli.c>
  # Compress text-based content types with Brotli
  AddOutputFilterByType BROTLI_COMPRESS text/plain
  AddOutputFilterByType BROTLI_COMPRESS text/html
  AddOutputFilterByType BROTLI_COMPRESS text/xml
  AddOutputFilterByType BROTLI_COMPRESS text/css
  AddOutputFilterByType BROTLI_COMPRESS text/javascript
  AddOutputFilterByType BROTLI_COMPRESS application/xml
  AddOutputFilterByType BROTLI_COMPRESS application/xhtml+xml
  AddOutputFilterByType BROTLI_COMPRESS application/javascript
  AddOutputFilterByType BROTLI_COMPRESS application/json
  AddOutputFilterByType BROTLI_COMPRESS application/ld+json
  AddOutputFilterByType BROTLI_COMPRESS font/opentype
  AddOutputFilterByType BROTLI_COMPRESS font/ttf
  AddOutputFilterByType BROTLI_COMPRESS font/woff
  AddOutputFilterByType BROTLI_COMPRESS font/woff2
  AddOutputFilterByType BROTLI_COMPRESS image/svg+xml

  # Brotli compression quality (0-11, default: 11)
  # Lower = faster compression, larger files
  # Higher = slower compression, smaller files
  BrotliCompressionQuality 6

  # Brotli window size (10-24, default: 22)
  BrotliCompressionWindow 22
</IfModule>

# Fallback: Use Gzip if Brotli is not available
# Both modules can coexist; Apache serves Brotli to
# browsers that support it and Gzip to others.

Common Mistakes and How to Fix Them

Debugging .htaccess issues can be frustrating. Here are the most common mistakes developers make and how to avoid them. Understanding these pitfalls will save you hours of troubleshooting.

Infinite Redirect Loops

The most common mistake is creating a redirect loop where the rewritten URL matches the same rule again. Always use RewriteCond to check the current state before applying a rule, and use the [L] flag to stop processing after a match.

# BAD: Creates an infinite loop
# (The rewritten URL "/new" matches "^(.*)$" again)
RewriteRule ^(.*)$ /new/$1 [R=301]

# GOOD: Use RewriteCond to prevent the loop
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/new/ [NC]
RewriteRule ^(.*)$ /new/$1 [R=301,L]

# GOOD: Another approach - check if already redirected
RewriteEngine On
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(.*)$ /new/$1 [R=301,L]

Wrong RewriteBase

RewriteBase defines the base URL path for per-directory rewrites. If your .htaccess is in a subdirectory, the RewriteBase must match that path. Using the wrong value causes rules to generate incorrect URLs.

# If .htaccess is in the document root:
RewriteEngine On
RewriteBase /

# If .htaccess is in /blog/ subdirectory:
RewriteEngine On
RewriteBase /blog/

# If .htaccess is in /app/public/ subdirectory:
RewriteEngine On
RewriteBase /app/public/

# Common mistake: Using RewriteBase /blog when file is at root
# This causes all rewritten URLs to be prefixed with /blog

Rule Order Matters

Apache processes .htaccess rules from top to bottom. If a more general rule appears before a specific one, it will match first and the specific rule will never execute. Always place specific rules before general ones, and use the [L] (Last) flag to prevent further processing.

# BAD: General rule before specific rule
RewriteEngine On
RewriteRule ^(.*)$ /index.php?page=$1 [L]        # Catches everything!
RewriteRule ^about$ /about-us.html [R=301,L]      # Never reached!

# GOOD: Specific rules first, general rules last
RewriteEngine On
RewriteRule ^about$ /about-us.html [R=301,L]      # Specific: runs first
RewriteRule ^contact$ /contact-us.html [R=301,L]  # Specific: runs second
RewriteCond %{REQUEST_FILENAME} !-f                # Skip existing files
RewriteCond %{REQUEST_FILENAME} !-d                # Skip existing dirs
RewriteRule ^(.*)$ /index.php?page=$1 [L]          # General: fallback
MistakeFix
Missing RewriteEngine OnAlways add "RewriteEngine On" at the top of your rewrite rules block
Not escaping dots in patternsUse \. instead of . in regex patterns (dot matches any character)
Forgetting the [L] flagAdd [L] to stop rule processing after a match and prevent cascading issues
Using Redirect and RewriteRule togetherStick to one method; mixing them causes unpredictable behavior
Not testing with browser cache cleared301 redirects are cached by browsers; use 302 during testing, switch to 301 in production
Wrong file permissions on .htaccessSet permissions to 644 (owner read/write, group/others read)

Frequently Asked Questions

What is .htaccess and where should I place it?

.htaccess (hypertext access) is a distributed configuration file for Apache web servers. Place it in the root directory of your website (usually public_html or www). It affects the directory it is in and all subdirectories. You can also place additional .htaccess files in subdirectories to override parent rules. The file must be named exactly ".htaccess" with the leading dot.

What is the difference between 301 and 302 redirects?

A 301 redirect is permanent -- it tells search engines to transfer all link equity (SEO value) to the new URL and to update their index. A 302 redirect is temporary -- search engines keep the original URL in their index. Use 301 for permanent moves (domain changes, restructured URLs) and 302 for temporary situations (A/B testing, maintenance pages). Browsers cache 301 redirects aggressively, so use 302 during testing.

Why are my .htaccess rules not working?

The most common reasons are: 1) mod_rewrite is not enabled -- run "a2enmod rewrite" and restart Apache; 2) AllowOverride is set to None in the Apache config -- change it to "AllowOverride All" for your directory; 3) The .htaccess file has incorrect permissions (should be 644); 4) Syntax errors -- check the Apache error log at /var/log/apache2/error.log; 5) Browser caching a previous 301 redirect -- clear your browser cache or test in incognito mode.

Does .htaccess work with Nginx?

No, .htaccess is specific to the Apache web server. Nginx does not support .htaccess files. If you are using Nginx, you must add equivalent directives directly to the Nginx server block configuration. There are online tools that can help convert common .htaccess rules to Nginx configuration syntax, but the conversion is not always one-to-one.

How do I redirect old URLs after a site redesign?

Create a mapping of old URLs to new URLs and add a Redirect or RewriteRule for each one. For large-scale changes, use RewriteMap with an external file. For pattern-based changes (like /blog/post-name to /articles/post-name), use RewriteRule with regex capture groups: RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]. Always test redirects with a tool like curl -I to verify the status code and destination.

How does .htaccess affect site performance?

Apache reads .htaccess files on every request, which adds a small performance overhead. For high-traffic sites, it is better to move rules to the main Apache configuration (httpd.conf or virtual host file) and set AllowOverride to None. However, for most websites the performance impact is negligible. The convenience of .htaccess (no server restart required, works on shared hosting) outweighs the minimal performance cost.

This .htaccess cheat sheet covers the most essential redirect patterns, security configurations, and performance optimizations. Always test your changes thoroughly using curl or an online redirect checker before deploying to production, and keep a backup of your working .htaccess file.

𝕏 Twitterin LinkedIn
Was this helpful?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Try These Related Tools

.ht.htaccess GeneratorNXNginx Config Generator%20URL Encoder/Decoder🤖Robots.txt Generator

Related Articles

Nginx Config Examples: Reverse Proxy, SSL, and Static Sites

Production-ready Nginx configuration examples for reverse proxy, SSL/TLS, static file serving, load balancing, and security headers.

Content Security Policy (CSP) Complete Guide: From Basics to Production

Learn Content Security Policy from scratch. Covers all CSP directives, common configurations, reporting, and step-by-step deployment guide.