🌐

htaccess

11 notes  •  Web Hosting

Redirect HTTP and www to HTTPS in .htaccess

Force all traffic to HTTPS using .htaccess mod_rewrite rules.

Redirect HTTP to HTTPS (Any Domain)

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

Redirect Both HTTP and www to HTTPS Non-www

RewriteEngine On
# Redirect www to non-www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
# Redirect HTTP to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Redirect www to HTTPS www

RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://www.%1%{REQUEST_URI} [R=301,L]

Notes

  • Place these rules at the top of .htaccess, before WordPress or other rewrite rules.
  • Use R=301 (permanent) for production, R=302 (temporary) while testing.
  • Test with curl -I http://yourdomain.com to verify the redirect chain.

Add Browser Cache Expiry Headers in .htaccess

Enable browser caching and gzip compression for static assets via .htaccess to improve page load performance.

Enable Expires Headers


    ExpiresActive On
    ExpiresDefault "access plus 1 month"

    # Images
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png  "access plus 1 year"
    ExpiresByType image/gif  "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"

    # CSS and JavaScript
    ExpiresByType text/css              "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
    ExpiresByType text/javascript       "access plus 1 year"

    # Fonts
    ExpiresByType font/woff2             "access plus 1 year"
    ExpiresByType application/x-font-ttf "access plus 1 year"
    ExpiresByType application/vnd.ms-fontobject "access plus 1 year"

    # HTML and data
    ExpiresByType text/html              "access plus 0 seconds"
    ExpiresByType application/json       "access plus 0 seconds"

Set Font MIME Types

AddType application/vnd.ms-fontobject .eot
AddType application/x-font-ttf .ttf
AddType application/x-font-opentype .otf
AddType application/x-font-woff .woff
AddType font/woff2 .woff2
AddType image/svg+xml .svg

Enable Gzip Compression for Fonts


    AddOutputFilterByType DEFLATE text/css application/javascript text/javascript
    AddOutputFilterByType DEFLATE application/x-font-ttf application/x-font-opentype image/svg+xml

Change Apache's Main Listening IP Address

How to configure Apache to listen on a new IP address, typically needed when the server's IP changes or you add a second IP.

Step 1 — Add a VirtualHost Config File

Create a config file named after the new IP in the Apache conf directory:

# Debian/Ubuntu: /etc/apache2/conf.d/
# CentOS/RHEL:   /etc/httpd/conf.d/

# Example: /etc/apache2/conf.d/162.245.216.135.conf
NameVirtualHost 162.245.216.135:80
Listen 162.245.216.135:80

NameVirtualHost 162.245.216.135:443
Listen 162.245.216.135:443

Step 2 — Update VirtualHost Blocks

Update your VirtualHost config files to use the new IP:


    ServerName example.com
    DocumentRoot /var/www/html/example.com

Step 3 — Test and Restart Apache

apachectl configtest
sudo systemctl restart apache2

Verify

sudo ss -tlnp | grep apache
curl -H "Host: example.com" http://162.245.216.135/

.htaccess File Reference Guide

The .htaccess file allows per-directory Apache configuration without modifying the main server config. It is placed in the web root or any subdirectory.

Enable mod_rewrite

RewriteEngine On

Force HTTPS

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

Block Access to a File or Directory

# Block access to wp-config.php

    Order Allow,Deny
    Deny from all


# Block access to a directory

    Order Allow,Deny
    Deny from all

Custom Error Pages

ErrorDocument 404 /404.html
ErrorDocument 500 /error.html

Gzip Compression


    AddOutputFilterByType DEFLATE text/html text/css application/javascript
    AddOutputFilterByType DEFLATE text/plain application/json application/xml

Password-Protect a Directory

AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

Prevent Directory Listing

Options -Indexes

Set PHP Values

php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value memory_limit 256M
php_value max_execution_time 300

Redirect Old URL to New URL

Redirect 301 /old-page.html /new-page.html

Enable Gzip Compression in .htaccess

Gzip compression can reduce page size by 60–80%, significantly improving load time. Enable it via .htaccess on Apache servers.

Enable Deflate Compression


    # Compress HTML, CSS, JavaScript, Text, XML, and fonts
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/json
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE image/svg+xml
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/javascript
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE font/woff2
    AddOutputFilterByType DEFLATE application/x-font-ttf

    # Remove browser bugs (only needed for old browsers)
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch MSIE !no-gzip !gzip-only-text/html
    Header append Vary User-Agent

Verify Compression is Working

curl -H "Accept-Encoding: gzip" -I https://example.com/
# Look for: Content-Encoding: gzip

Alternative — mod_gzip (Older Apache)


    mod_gzip_on Yes
    mod_gzip_dechunk Yes
    mod_gzip_item_include file \.(html?|txt|css|js|php|pl)$
    mod_gzip_item_include mime ^application/x-javascript.*
    mod_gzip_item_include mime ^text/.*
    mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*

WordPress Default .htaccess Configuration

The standard .htaccess file for WordPress, which enables pretty permalinks and proper URL routing.

Standard WordPress .htaccess

# BEGIN WordPress

RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# END WordPress

WordPress in a Subdirectory

If WordPress is installed in a subdirectory (e.g., /blog), use:

# BEGIN WordPress

RewriteEngine On
RewriteBase /blog/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]

# END WordPress

WordPress Multisite .htaccess

# BEGIN WordPress Multisite

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]

# Add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

# END WordPress Multisite

Set Browser Cache Expiry Headers in Nginx

Configure long cache expiry for static assets in Nginx to improve site performance via browser caching.

Add Expires Headers in Nginx Virtual Host

server {
    # ... other config ...

    # Cache images, CSS, JS, and fonts for 1 year
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|webp|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        log_not_found off;
        access_log off;
    }

    # Cache HTML for a short time
    location ~* \.(html|htm)$ {
        expires 1h;
        add_header Cache-Control "public";
    }
}

Alternative — Match Multiple Extensions

location ~* ^.+\.(ico|css|js|gif|jpe?g|png|woff|woff2|eot|svg|ttf)$ {
    expires 1y;
    add_header Pragma public;
    add_header Cache-Control "public";
}

Verify

curl -I https://example.com/style.css
# Look for: Cache-Control: public, max-age=31536000

Fix CORS Issues in .htaccess

Cross-Origin Resource Sharing (CORS) errors occur when a browser blocks requests between different origins. Add appropriate headers in .htaccess to allow cross-origin access.

Allow All Origins (Simple — Not Recommended for Production)


    Header set Access-Control-Allow-Origin "*"

Allow a Specific Origin


    Header set Access-Control-Allow-Origin "https://app.example.com"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"

Allow Multiple Origins Dynamically

SetEnvIf Origin "^https://(app|api)\.example\.com$" ORIGIN_OK=$0


    Header set Access-Control-Allow-Origin "%{ORIGIN_OK}e" env=ORIGIN_OK
    Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"

Handle OPTIONS Preflight Requests

RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

.htaccess Rewrite Rules for Common Frameworks

Common .htaccess rewrite rules for routing all requests through a front controller (e.g., for Laravel, Slim, CodeIgniter).

Laravel / Slim Framework

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?path=$1 [NC,L,QSA]

CodeIgniter

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]

Symfony

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ public/index.php [QSA,L]

Remove index.php from URL

RewriteEngine On
RewriteCond %{THE_REQUEST} /index\.php [NC]
RewriteRule ^(.*)index\.php(.*)$ /$1$2 [R=301,L]

Allow Access to .well-known Folder in .htaccess

Some WordPress and CMS .htaccess configurations block access to the .well-known directory, which is required for domain verification (AutoSSL, Let's Encrypt, DCV).

Allow .well-known Access

Add this rule before the WordPress or main rewrite block in .htaccess:

RewriteEngine On

# Allow .well-known and validation files through
RewriteCond %{REQUEST_URI} /[A-F0-9]{32}\.txt$ [OR]
RewriteCond %{REQUEST_URI} /\.well\-known\/pki\-validation
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule (.*) $1 [L]

Alternative — Direct Allow Rule


RewriteEngine On
RewriteRule ^\.well-known/ - [L]

Alternative — Using FilesMatch


    Order Allow,Deny
    Allow from all
    Satisfy Any

Allow Subdirectory Access Within a WordPress Site

WordPress's .htaccess by default routes all requests through index.php, which can block direct access to subdirectories or standalone apps installed in a subdirectory. Here's how to exempt a specific directory.

Exempt a Subdirectory from WordPress Routing

Add this rule before the WordPress block:

RewriteEngine On
RewriteBase /

# Bypass WordPress for /myapp/
RewriteRule ^myapp(/.*)?$ - [L]

# BEGIN WordPress

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# END WordPress

Alternative — For Files Inside the Subdirectory

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} ^/mydirectoryhere/(.*)$
RewriteRule ^.*$ - [L]

Notes

  • The - [L] flag means "do nothing and stop processing rules" — the request is passed through as-is.
  • Place exemption rules before the WordPress rewrite block to ensure they take effect first.