.htaccess Configuration in Apache

The .htaccess file is one of the most powerful configuration tools in Apache web server, allowing you to implement URL rewrites, redirects, security policies, and performance optimizations without requiring server restart or root access. Understanding .htaccess configuration is essential for web developers, system administrators, and anyone managing Apache-based websites who needs fine-grained control over directory-level web server behavior.

This comprehensive guide covers everything from basic .htaccess setup to advanced configurations including URL rewriting, access control, caching directives, security hardening, and performance optimization. Whether you're managing WordPress installations, implementing SEO-friendly URLs, enforcing HTTPS, or protecting sensitive directories, mastering .htaccess configuration will give you powerful control over your Apache web server behavior at the directory level.

Table of Contents

Prerequisites

Before working with .htaccess files, ensure you have:

  • Apache Web Server: Version 2.4 or later installed and running
  • Operating System: Ubuntu 20.04/22.04, Debian 10/11, CentOS 8/Rocky Linux 8, or similar
  • File access: Ability to create and edit files in web directories
  • Basic knowledge: Understanding of HTTP, URLs, and web server concepts
  • Text editor: nano, vim, or any text editor for creating .htaccess files
  • Website or application: Running website or web application on Apache
  • Backup: Current backup of existing .htaccess files (if any)

Note: While .htaccess provides convenience, it comes with a performance cost. For high-traffic production servers, consider placing directives in main Apache configuration files instead.

Understanding .htaccess

What is .htaccess?

.htaccess (hypertext access) is a directory-level configuration file for Apache web server. It allows you to override server configuration settings for specific directories and their subdirectories without modifying the main Apache configuration or requiring server restart.

Key characteristics:

  • Directory-specific: Applies only to the directory it's in and subdirectories
  • No restart required: Changes take effect immediately
  • Cascading: Settings cascade to subdirectories unless overridden
  • Limited scope: Cannot override all Apache directives
  • Performance impact: Checked on every request, adding overhead

How .htaccess Works

  1. Request arrives: Client makes HTTP request for a file
  2. Directory traversal: Apache walks up the directory tree from root to requested file's directory
  3. File check: Looks for .htaccess in each directory
  4. Rule application: Applies directives from all found .htaccess files
  5. Cascading: Child directory rules override parent directory rules
  6. Request processing: Continues with modified configuration

When to Use .htaccess

Use .htaccess when:

  • You don't have root access to main Apache configuration
  • You need per-directory configuration in shared hosting
  • You're managing multiple sites with different requirements
  • You need to delegate configuration to non-admin users

Avoid .htaccess when:

  • You have root access and can use main configuration
  • Running high-traffic production servers (performance reasons)
  • You need server-wide configuration
  • Security is critical (main config is more secure)

Enabling .htaccess

Check if .htaccess is Enabled

First, verify if .htaccess support is enabled:

# Check Apache configuration
apache2ctl -M | grep rewrite

# Check AllowOverride directive
grep -r "AllowOverride" /etc/apache2/

Enable .htaccess Support

Edit your Apache virtual host configuration:

# Ubuntu/Debian
sudo nano /etc/apache2/sites-available/your-site.conf

# CentOS/Rocky Linux
sudo nano /etc/httpd/conf/httpd.conf

Add or modify the Directory directive:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html

    <Directory /var/www/html>
        # Enable .htaccess overrides
        AllowOverride All

        # Alternative: Enable specific overrides only
        # AllowOverride FileInfo AuthConfig Limit

        # Require Apache 2.4+
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

AllowOverride options:

  • All: Allow all overrides (maximum flexibility)
  • None: Disable .htaccess (best performance)
  • FileInfo: Allow file-type directives
  • AuthConfig: Allow authentication directives
  • Limit: Allow access control directives
  • Options: Allow Options directive

Enable Required Apache Modules

Enable mod_rewrite and other modules needed for .htaccess:

# Enable mod_rewrite (required for URL rewriting)
sudo a2enmod rewrite

# Enable mod_headers (for header manipulation)
sudo a2enmod headers

# Enable mod_expires (for cache control)
sudo a2enmod expires

# Enable mod_deflate (for compression)
sudo a2enmod deflate

# Restart Apache to apply changes
sudo systemctl restart apache2

For CentOS/Rocky Linux:

# Modules are typically enabled by default
# Verify in /etc/httpd/conf.modules.d/

# Restart Apache
sudo systemctl restart httpd

Basic Configuration

Creating Your First .htaccess File

Create a .htaccess file in your website's root directory:

# Navigate to web root
cd /var/www/html

# Create .htaccess file
sudo nano .htaccess

Basic .htaccess template:

# Enable RewriteEngine
RewriteEngine On

# Set default character set
AddDefaultCharset UTF-8

# Disable directory browsing
Options -Indexes

# Follow symbolic links
Options +FollowSymLinks

# Custom error pages
ErrorDocument 404 /404.html
ErrorDocument 500 /500.html

# Protect .htaccess file itself
<Files .htaccess>
    Require all denied
</Files>

Save the file and set proper permissions:

# Set ownership
sudo chown www-data:www-data .htaccess  # Ubuntu/Debian
# sudo chown apache:apache .htaccess    # CentOS/Rocky

# Set permissions (read/write for owner, read for group/others)
sudo chmod 644 .htaccess

Directory Index Configuration

Control which files serve as directory index:

# Default index files in order of preference
DirectoryIndex index.html index.php index.htm default.html

# Disable directory index entirely
Options -Indexes

# Custom index file
DirectoryIndex home.php

# Multiple index options
DirectoryIndex index.php index.html index.htm

File Type Configuration

Configure handling of specific file types:

# Set PHP handler
AddHandler application/x-httpd-php .php .php5 .php7

# Force download for specific file types
<FilesMatch "\.(zip|pdf|doc|docx)$">
    Header set Content-Disposition attachment
</FilesMatch>

# Disable execution of scripts in uploads directory
<Directory "uploads">
    Options -ExecCGI
    AddHandler cgi-script .php .pl .py .jsp .asp .sh .cgi
    Require all granted
</Directory>

URL Rewriting and Redirects

Basic Redirects

Implement common redirect scenarios:

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

# Redirect entire directory
Redirect 301 /old-directory /new-directory

# Redirect to external URL
Redirect 301 /external https://example.com

# Temporary redirect (302)
Redirect 302 /temporary /new-location

HTTP to HTTPS Redirect

Force all traffic to use HTTPS:

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

# Method 2: Check for non-standard HTTPS port
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Method 3: Behind load balancer/proxy
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

WWW Redirection

Enforce www or non-www URL format:

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

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

Clean URLs (Remove File Extensions)

Create SEO-friendly URLs by removing file extensions:

# Remove .php extension
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.+)$ $1.php [L]

# Remove .html extension
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.+)$ $1.html [L]

# Remove multiple extensions
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/]+)/$ $1.php [L]
RewriteRule ^([^/]+)/([^/]+)/$ /$1/$2.php [L]

Pretty URLs for Dynamic Content

Transform query string URLs to clean paths:

# Convert: /product.php?id=123 to /product/123
RewriteEngine On
RewriteRule ^product/([0-9]+)/?$ product.php?id=$1 [L,QSA]

# Convert: /category.php?name=electronics to /category/electronics
RewriteRule ^category/([a-zA-Z0-9-]+)/?$ category.php?name=$1 [L,QSA]

# Convert: /user.php?username=john&action=profile to /user/john/profile
RewriteRule ^user/([a-zA-Z0-9]+)/([a-zA-Z]+)/?$ user.php?username=$1&action=$2 [L,QSA]

Advanced URL Rewriting

Complex rewrite scenarios:

RewriteEngine On

# Remove trailing slashes (except for directories)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]

# Add trailing slash to directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} !(.+)/$
RewriteRule ^(.+)$ $1/ [L,R=301]

# Block direct access to PHP files (route through index.php)
RewriteCond %{REQUEST_FILENAME} !index\.php
RewriteCond %{REQUEST_FILENAME} \.php$
RewriteRule ^(.*)$ index.php [L]

# Language-based routing
RewriteRule ^en/(.*)$ /$1?lang=en [L,QSA]
RewriteRule ^es/(.*)$ /$1?lang=es [L,QSA]
RewriteRule ^fr/(.*)$ /$1?lang=fr [L,QSA]

Access Control

IP-Based Access Control

Restrict access based on IP addresses:

# Allow specific IPs only
<RequireAll>
    Require ip 192.168.1.100
    Require ip 192.168.1.101
    Require ip 10.0.0.0/24
</RequireAll>

# Deny specific IPs
<RequireAll>
    Require all granted
    Require not ip 192.168.1.200
    Require not ip 10.0.1.0/24
</RequireAll>

# Allow localhost only
Require local

# Allow from specific domains
Require host example.com
Require host .trusted-domain.com

Password Protection

Protect directories with password authentication:

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

# Require specific users
Require user john jane admin

# Require group membership
AuthGroupFile /etc/apache2/.htgroups
Require group admins

Create password file:

# Install htpasswd utility (if not installed)
sudo apt install apache2-utils

# Create password file and first user
sudo htpasswd -c /etc/apache2/.htpasswd username

# Add additional users (without -c flag)
sudo htpasswd /etc/apache2/.htpasswd another_user

# Set proper permissions
sudo chmod 640 /etc/apache2/.htpasswd
sudo chown root:www-data /etc/apache2/.htpasswd

Protect Specific Files

Restrict access to individual files:

# Protect single file
<Files "config.php">
    Require all denied
</Files>

# Protect multiple files by name
<FilesMatch "(config|database|settings)\.php$">
    Require all denied
</FilesMatch>

# Protect file types
<FilesMatch "\.(log|sql|txt|ini)$">
    Require all denied
</FilesMatch>

# Password protect specific file
<Files "admin.php">
    AuthType Basic
    AuthName "Admin Area"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</Files>

Protect Directories

Secure entire directories:

# Deny access to uploads directory execution
<Directory "uploads">
    Options -ExecCGI -Includes
    RemoveHandler .php .phtml .php3 .php4 .php5 .phps .shtml .cgi .pl .py
    RemoveType .php .phtml .php3 .php4 .php5 .phps
    php_flag engine off
    Require all granted
</Directory>

# Protect admin directory
<Directory "admin">
    AuthType Basic
    AuthName "Admin Access"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</Directory>

Security Configuration

Prevent Directory Listing

Disable directory browsing:

# Disable directory indexes
Options -Indexes

# Or show custom index page
Options +Indexes
IndexOptions FancyIndexing

Hide Sensitive Files

Protect configuration and sensitive files:

# Protect various sensitive files
<FilesMatch "(^#.*#|\.(bak|config|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$">
    Require all denied
</FilesMatch>

# Protect .htaccess and .htpasswd files
<Files ~ "^\.ht">
    Require all denied
</Files>

# Protect environment files
<Files ".env">
    Require all denied
</Files>

# Protect backup files
<FilesMatch "\.(bak|backup|old|orig|save)$">
    Require all denied
</FilesMatch>

Prevent Hotlinking

Stop others from embedding your images:

# Basic hotlink protection
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp)$ - [F,NC,L]

# Redirect hotlinked images to placeholder
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ /images/hotlink-placeholder.png [R,NC,L]

# Allow specific trusted domains
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?(example\.com|trusted-site\.com) [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ - [F,NC,L]

Security Headers

Implement security headers:

# Require mod_headers enabled
<IfModule mod_headers.c>
    # Prevent clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"

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

    # Enable XSS protection
    Header always set X-XSS-Protection "1; mode=block"

    # HTTP Strict Transport Security (HSTS)
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    # Content Security Policy
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"

    # Referrer Policy
    Header always set Referrer-Policy "strict-origin-when-cross-origin"

    # Permissions Policy
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

    # Remove server signature
    Header unset Server
    Header unset X-Powered-By
</IfModule>

Block Bad Bots and User Agents

Prevent malicious bots and scrapers:

# Block specific user agents
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (bot|crawler|spider|scraper) [NC,OR]
RewriteCond %{HTTP_USER_AGENT} (wget|curl|libwww) [NC]
RewriteRule .* - [F,L]

# Block empty user agents
RewriteCond %{HTTP_USER_AGENT} ^$
RewriteRule .* - [F,L]

# More comprehensive bot blocking
SetEnvIfNoCase User-Agent "^libwww-perl" bad_bot
SetEnvIfNoCase User-Agent "^wget" bad_bot
SetEnvIfNoCase User-Agent "^curl" bad_bot
SetEnvIfNoCase User-Agent "Indy Library" bad_bot
SetEnvIfNoCase user-Agent "^java" bad_bot
SetEnvIfNoCase user-Agent "^Widow" bad_bot

<RequireAll>
    Require all granted
    Require not env bad_bot
</RequireAll>

Performance Optimization

Enable Compression

Compress text-based content for faster delivery:

<IfModule mod_deflate.c>
    # Compress HTML, CSS, JavaScript, Text, XML and fonts
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
    AddOutputFilterByType DEFLATE application/x-font
    AddOutputFilterByType DEFLATE application/x-font-opentype
    AddOutputFilterByType DEFLATE application/x-font-otf
    AddOutputFilterByType DEFLATE application/x-font-truetype
    AddOutputFilterByType DEFLATE application/x-font-ttf
    AddOutputFilterByType DEFLATE application/x-javascript
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE font/opentype
    AddOutputFilterByType DEFLATE font/otf
    AddOutputFilterByType DEFLATE font/ttf
    AddOutputFilterByType DEFLATE image/svg+xml
    AddOutputFilterByType DEFLATE image/x-icon
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/javascript
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/xml

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

Browser Caching

Configure caching for static resources:

<IfModule mod_expires.c>
    # Enable expirations
    ExpiresActive On

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

    # Images: 1 year
    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"
    ExpiresByType image/x-icon "access plus 1 year"

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

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

    # Documents: 1 week
    ExpiresByType application/pdf "access plus 1 week"
    ExpiresByType application/vnd.ms-excel "access plus 1 week"

    # HTML: 1 hour
    ExpiresByType text/html "access plus 1 hour"
</IfModule>

# Alternative: Cache-Control headers
<IfModule mod_headers.c>
    # 1 YEAR - fonts, images
    <FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|webp|svg|woff|woff2|ttf|eot)$">
        Header set Cache-Control "max-age=31536000, public"
    </FilesMatch>

    # 1 MONTH - CSS and JavaScript
    <FilesMatch "\.(css|js)$">
        Header set Cache-Control "max-age=2628000, public"
    </FilesMatch>

    # 1 HOUR - HTML
    <FilesMatch "\.(html|htm)$">
        Header set Cache-Control "max-age=3600, public"
    </FilesMatch>
</IfModule>

ETags Configuration

Configure ETags for better caching:

# Remove ETags (some prefer this for cache consistency)
<IfModule mod_headers.c>
    Header unset ETag
</IfModule>
FileETag None

# Or configure ETags properly
FileETag MTime Size

Error Handling

Custom Error Pages

Define custom error pages for better user experience:

# 404 Not Found
ErrorDocument 404 /errors/404.html

# 403 Forbidden
ErrorDocument 403 /errors/403.html

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

# 503 Service Unavailable
ErrorDocument 503 /errors/503.html

# Multiple error codes to same page
ErrorDocument 400 /errors/4xx.html
ErrorDocument 401 /errors/4xx.html
ErrorDocument 403 /errors/4xx.html
ErrorDocument 404 /errors/4xx.html

# Redirect to external URL
ErrorDocument 404 https://example.com/404

# Display simple message
ErrorDocument 404 "Page not found. Please check the URL."

Maintenance Mode

Implement site-wide maintenance mode:

# Maintenance mode - redirect all except specific IP
RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^123\.456\.789\.000$
RewriteCond %{REQUEST_URI} !^/maintenance\.html$
RewriteRule ^(.*)$ /maintenance.html [R=307,L]

# Alternative: Show maintenance page except for allowed IPs
RewriteEngine On
RewriteCond %{REQUEST_URI} !/maintenance.html$
RewriteCond %{REMOTE_ADDR} !^123\.456\.789\.000$
RewriteRule $ /maintenance.html [R=302,L]

Verification and Testing

Test .htaccess Syntax

Verify configuration syntax:

# Test Apache configuration (includes .htaccess)
sudo apache2ctl configtest

# Expected output: Syntax OK

# Check for errors in Apache error log
sudo tail -f /var/log/apache2/error.log

# Test with a test file
echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/test.php
curl http://localhost/test.php

Test Redirects

Verify redirects are working:

# Test HTTP to HTTPS redirect
curl -I http://example.com
# Should show: HTTP/1.1 301 Moved Permanently
# Location: https://example.com

# Test WWW redirect
curl -I http://example.com
# Should redirect to www.example.com or vice versa

# Test 404 error page
curl -I http://example.com/nonexistent-page
# Should show: HTTP/1.1 404 Not Found

Test URL Rewrites

Verify URL rewriting:

# Test clean URLs
curl -I http://example.com/product/123
# Should return 200 OK if rewrite works

# Test with browser developer tools
# Open Network tab and check request/response

# Check rewrite rules
sudo apache2ctl -M | grep rewrite

Test Access Control

Verify access restrictions:

# Test IP-based blocking (from different IP)
curl -I http://example.com

# Test password protection
curl -u username:password http://example.com/protected/

# Test file protection
curl -I http://example.com/.htaccess
# Should return: HTTP/1.1 403 Forbidden

Test Performance Optimizations

Verify caching and compression:

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

# Test caching headers
curl -I http://example.com/style.css
# Look for: Cache-Control and Expires headers

# Use online tools
# GTmetrix: https://gtmetrix.com
# WebPageTest: https://www.webpagetest.org

Troubleshooting

500 Internal Server Error

Issue: .htaccess causes 500 error

Diagnosis:

# Check Apache error log
sudo tail -50 /var/log/apache2/error.log

# Common error messages:
# - "Invalid command 'RewriteEngine'" - mod_rewrite not enabled
# - "Options not allowed here" - AllowOverride not set properly

Solutions:

# Enable required modules
sudo a2enmod rewrite headers expires

# Check AllowOverride in virtual host
# Should be: AllowOverride All

# Test Apache configuration
sudo apache2ctl configtest

# Restart Apache
sudo systemctl restart apache2

Redirect Loops

Issue: Too many redirects error

Cause: Conflicting redirect rules

Solution:

# Check for circular redirects
# Bad example:
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Fix: Use conditions to prevent loops
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Add RewriteBase if using subdirectory
RewriteBase /subdirectory/

URL Rewriting Not Working

Issue: Rewrite rules not applied

Diagnosis:

# Check if mod_rewrite is enabled
apache2ctl -M | grep rewrite

# Check AllowOverride setting
grep -r "AllowOverride" /etc/apache2/

# Enable rewrite logging
# Add to virtual host:
LogLevel alert rewrite:trace3

Solution:

# Enable mod_rewrite
sudo a2enmod rewrite

# Ensure AllowOverride is set
# In virtual host configuration:
AllowOverride All

# Restart Apache
sudo systemctl restart apache2

.htaccess Not Being Read

Issue: Changes not taking effect

Solutions:

# Check file name (must be exactly .htaccess)
ls -la /var/www/html/ | grep htaccess

# Check file permissions
sudo chmod 644 /var/www/html/.htaccess

# Check AccessFileName directive
grep AccessFileName /etc/apache2/apache2.conf
# Should show: AccessFileName .htaccess

# Verify AllowOverride is not "None"
grep -r "AllowOverride None" /etc/apache2/

# Clear browser cache
# Some issues are due to cached redirects

Best Practices

Performance Considerations

  1. Use main Apache config when possible:

    • .htaccess is checked on every request
    • Main config is read once at startup
    • For production, prefer virtual host config
  2. Minimize .htaccess file size:

    • Remove unnecessary comments
    • Use only required directives
    • Avoid redundant rules
  3. Use specific conditions:

# Inefficient: Checks every request
RewriteEngine On
RewriteRule .* - [E=CACHE:1]

# Efficient: Checks only relevant requests
<FilesMatch "\.(jpg|png|css|js)$">
    Header set Cache-Control "max-age=31536000"
</FilesMatch>

Security Best Practices

  1. Protect .htaccess file:
<Files .htaccess>
    Require all denied
</Files>
  1. Set proper file permissions:
sudo chmod 644 .htaccess
sudo chown www-data:www-data .htaccess
  1. Use HTTPS redirects:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
  1. Hide sensitive information:
<FilesMatch "\.(log|sql|conf|ini)$">
    Require all denied
</FilesMatch>

Organization and Maintenance

  1. Add comments:
# ==============================
# SECURITY CONFIGURATION
# ==============================

# Disable directory listing
Options -Indexes

# ==============================
# URL REWRITING
# ==============================

# Force HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
  1. Use consistent formatting:
# Good formatting
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
  1. Test before deploying:
    • Always backup existing .htaccess
    • Test in development environment first
    • Monitor error logs after deployment

Common Pitfalls to Avoid

  1. Don't use .htaccess for server-wide settings
  2. Avoid overly complex regex patterns
  3. Don't forget the [L] flag on final rewrite rules
  4. Don't create redirect loops
  5. Don't expose sensitive files or directories

Conclusion

The .htaccess file is an incredibly powerful tool for Apache web server configuration, providing directory-level control over URL rewriting, access control, security policies, and performance optimization. While it offers convenience and flexibility, especially in shared hosting environments, understanding its proper use and limitations is crucial for maintaining secure, performant web applications.

Key takeaways from this guide:

  • Enable properly: Ensure mod_rewrite and AllowOverride are correctly configured
  • URL rewriting: Master redirect and rewrite rules for SEO-friendly URLs
  • Access control: Implement IP-based and password-based restrictions
  • Security: Protect sensitive files, prevent hotlinking, and add security headers
  • Performance: Enable compression and caching for faster page loads
  • Testing: Always test configuration changes and monitor error logs
  • Best practices: Use main config for production when possible, maintain clean code

While .htaccess provides tremendous flexibility, remember that for high-traffic production environments, moving directives to the main Apache configuration files offers better performance. The .htaccess file is checked on every request, adding overhead that can be avoided by using virtual host configuration.

Continue learning by exploring advanced topics like mod_security rules, advanced regex patterns for URL rewriting, and integration with content management systems. Regular testing, monitoring, and optimization ensure your .htaccess configuration remains effective and secure as your web application evolves.