The .htaccess file (Hypertext Access) is a small per-directory configuration file that Apache Web Server reads on every request. It lets website owners adjust server behavior without touching the main httpd.conf — making it the primary customization tool available on shared hosting and DirectAdmin environments.
From URL redirects and forced HTTPS to GZIP compression, browser caching, security headers, and PHP setting overrides, .htaccess handles a wide range of tasks that would otherwise require server-level access. This guide collects the most commonly used .htaccess commands for WordPress sites and PHP web applications, with working examples tested on Apache 2.4.
Understanding How .htaccess Works
Before adding any directives, understanding three core principles will save you from debugging hours later:
- Parsed on every request — Unlike
httpd.conf, which loads once at server startup, Apache re-reads .htaccess on every HTTP request. This means changes take effect immediately without a server restart, but too many rules can add measurable overhead to each request. - Cascading scope — A .htaccess in
/public_html/applies to the entire site. A .htaccess inside/public_html/shop/applies only to URLs under/shop/. Apache merges files from root down to the request directory, with deeper files overriding parent files for the same directives. - AllowOverride must be enabled — If the server sets
AllowOverride None, Apache ignores .htaccess entirely. Most shared hosting providers, including AsiaGB DirectAdmin Hosting, enable this by default. If your directives have no effect, verify this setting first.
Lines beginning with # are comments and have no effect. Using comments generously makes future maintenance much easier, especially when you have multiple rule blocks covering different purposes.
WordPress Permalink Block — The Foundation
WordPress relies on mod_rewrite to route all requests that do not correspond to a real file or directory to index.php, which then handles the WordPress routing logic. WordPress Dashboard generates this block automatically when you save your Permalink settings under Settings > Permalinks. Here is what each line does:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
RewriteBase / sets the base path for relative rules. If WordPress is installed in a subdirectory such as /blog/, change this to RewriteBase /blog/ and update the final rule to RewriteRule . /blog/index.php [L]. The two RewriteCond lines ensure that requests for real files (images, CSS, JS) and real directories bypass WordPress routing and are served directly by Apache. Never remove these conditions — doing so will break static asset delivery.
Redirects — 301 and HTTPS Enforcement
Redirects are among the most common .htaccess tasks. The two most critical for any production site are forcing HTTPS and choosing a canonical domain (www vs non-www). Both should be set up from day one.
Force HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Redirect www to non-www (canonical non-www)
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.yourdomain\.com [NC]
RewriteRule ^(.*)$ https://yourdomain.com/$1 [R=301,L]
Combined: force HTTPS + non-www in one pass
Combining both rules prevents a double redirect (two 301s in sequence) which wastes latency and dilutes link equity:
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^(.*)$ https://yourdomain.com/$1 [R=301,L]
The [OR] flag means the rule fires when either condition is true. All four entry points (http/https × www/non-www) are redirected to the canonical URL in a single hop.
GZIP Compression — Smaller Transfers, Faster Pages
GZIP compresses text-based assets before sending them to the browser, typically reducing HTML, CSS, and JavaScript by 60–80%. This is one of the highest-impact performance optimizations available and has zero negative side effects on modern browsers.
<IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/html AddOutputFilterByType DEFLATE text/xml AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE application/xml AddOutputFilterByType DEFLATE application/xhtml+xml AddOutputFilterByType DEFLATE application/rss+xml AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE application/x-javascript AddOutputFilterByType DEFLATE application/json # Skip already-compressed formats SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|svg|ico|zip|gz|bz2|mp4|mp3)$ no-gzip dont-vary </IfModule>
Verify that GZIP is active with: curl -H "Accept-Encoding: gzip" -I https://yourdomain.com/. A Content-Encoding: gzip header in the response confirms it is working.
Browser Caching — Reduce Repeat Downloads
Browser caching tells the browser how long to keep static assets locally. A visitor who returns within the cache window skips downloading images, CSS, and fonts entirely, resulting in near-instant page loads and lower bandwidth consumption on your server.
<IfModule mod_expires.c> ExpiresActive On ExpiresDefault "access plus 1 month" # Images — cache for 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 — cache for 1 month ExpiresByType text/css "access plus 1 month" ExpiresByType application/javascript "access plus 1 month" ExpiresByType application/x-javascript "access plus 1 month" # Web fonts — cache for 1 year ExpiresByType font/woff2 "access plus 1 year" ExpiresByType font/woff "access plus 1 year" ExpiresByType application/font-woff2 "access plus 1 year" # HTML — short cache ExpiresByType text/html "access plus 1 hour" ExpiresByType application/rss+xml "access plus 1 hour" </IfModule> <IfModule mod_headers.c> Header append Cache-Control "public" </IfModule>
Cache Busting Tip: Long cache lifetimes are powerful but create a problem when you update a CSS or JS file — users see the old version until their cache expires. Solve this with cache busting: append a version query string to asset URLs, such as style.css?v=20260609. The browser treats the new URL as a different file and downloads the latest version immediately, bypassing the old cache entry entirely.
Security Headers
Security headers are HTTP response headers that instruct the browser on how to handle content, preventing entire classes of vulnerabilities such as clickjacking, MIME sniffing, and cross-site scripting. They are free to add, have no performance cost, and dramatically improve your security posture.
<IfModule mod_headers.c> # Prevent framing from other origins (clickjacking protection) Header always set X-Frame-Options "SAMEORIGIN" # Prevent MIME type sniffing Header always set X-Content-Type-Options "nosniff" # Legacy XSS protection for older browsers Header always set X-XSS-Protection "1; mode=block" # Control referrer information sent to third parties Header always set Referrer-Policy "strict-origin-when-cross-origin" # HTTP Strict Transport Security — set only after HTTPS is confirmed working Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" </IfModule>
Blocking xmlrpc.php (WordPress)
WordPress's xmlrpc.php is a legacy remote publishing interface that attackers routinely use for brute-force password attacks and amplification DDoS. If you do not use Jetpack or any XML-RPC client, block it entirely:
<Files xmlrpc.php> Order Deny,Allow Deny from all </Files>
Block PHP Execution in /uploads/ (WordPress)
Even if a file upload vulnerability allows an attacker to place a PHP file inside the uploads directory, this rule prevents Apache from executing it. Create a separate .htaccess file inside wp-content/uploads/:
<FilesMatch "\.php$"> Order Allow,Deny Deny from all </FilesMatch>
PHP Setting Overrides
On shared hosting where you cannot edit the global php.ini directly, you can override many PHP settings per-directory through .htaccess using php_value and php_flag directives. AsiaGB DirectAdmin Hosting supports this.
# Increase memory limit for WordPress or heavy PHP apps php_value memory_limit 256M # Increase file upload limits php_value upload_max_filesize 64M php_value post_max_size 64M # Increase maximum script execution time (seconds) php_value max_execution_time 300 # Hide PHP errors from public view (use on production) php_flag display_errors Off # Log errors to error_log instead of displaying them php_flag log_errors On # Set timezone for date/time functions php_value date.timezone "Asia/Bangkok" # Session security hardening php_value session.cookie_httponly 1 php_value session.cookie_secure 1
If adding php_value directives causes a 500 error, the hosting environment does not allow PHP overrides through .htaccess. In that case, try placing a php.ini or user.ini file in the same directory — many shared hosting setups support one of these alternatives.
Hotlink Protection and Access Control
Hotlinking occurs when another website embeds your images using your server's URL, consuming your bandwidth without serving your visitors. You can block this using the HTTP Referer header:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?yourdomain\.com [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|svg)$ - [F,NC]
</IfModule>
The first condition allows empty referers (direct access, curl, RSS readers). The second allows requests from your own domain. The [F] flag returns a 403 Forbidden response instead of serving the image. Replace yourdomain.com with your actual domain.
Quick-Reference Table
Use this table as a fast lookup when you need a specific directive category:
| Category | Key Directive | Apache Module | Purpose |
|---|---|---|---|
| Redirect | Redirect 301 |
mod_alias | Permanent URL moves, preserves SEO |
| Rewrite | RewriteRule / RewriteCond |
mod_rewrite | URL pattern transformation, WordPress routing |
| GZIP | AddOutputFilterByType DEFLATE |
mod_deflate | Compress responses, reduce bandwidth |
| Cache | ExpiresByType |
mod_expires | Set browser cache lifetime per file type |
| Headers | Header always set |
mod_headers | Security headers, HSTS, CORS |
| PHP | php_value / php_flag |
mod_php | Override php.ini settings per directory |
| Access | Order Deny,Allow / <Files> |
mod_access | Block files, IPs, or user agents |
| Error | ErrorDocument |
core | Serve custom error pages for 4xx/5xx |
Best Practices and Common Pitfalls
These guidelines will prevent the most common mistakes when working with .htaccess:
- Always back up before editing — Download the current .htaccess before every change. If something breaks, you can restore it in seconds via FTP or DirectAdmin File Manager.
- Wrap blocks in IfModule — Using
<IfModule mod_xxx.c>prevents a 500 error if the required module is not loaded on the server. - Rule order matters — Apache processes rules top to bottom. Broad rules (HTTPS enforcement) should come first. Specific rules should use the
[L]flag to stop processing after a match, preventing unintended downstream rule interactions. - Test in Incognito mode — 301 redirects are cached by browsers. Testing in a private/incognito window bypasses this cache and shows the actual server behavior after your changes.
- Check the error log — DirectAdmin provides access to the Apache error log under Logs > Error Log. If a directive causes a 500 error, the log will show the exact line and reason.
- Watch for redirect loops — A rule that matches its own output will loop indefinitely. Use
curl -L -Ito follow redirects and count the hops. More than two hops from the same origin URL indicates a loop or chain that needs fixing. - Do not fight WordPress — WordPress manages its own routing inside its
# BEGIN WordPress / # END WordPressblock. Add your custom rules above or below this block, never inside it, to prevent WordPress Dashboard from overwriting your changes when it regenerates Permalink rules.
Frequently Asked Questions
Does .htaccess work on all types of web hosting?
.htaccess only works on Apache-based web servers, which covers most shared hosting and VPS environments including AsiaGB DirectAdmin Hosting. If your server runs Nginx, you must configure directives in nginx.conf instead. LiteSpeed servers support nearly all Apache .htaccess directives as well.
My site shows 500 Internal Server Error after editing .htaccess — what do I do?
A 500 error from .htaccess is almost always caused by a syntax mistake: a misspelled directive, missing square brackets around a flag like [L], or mod_rewrite not being loaded. Check your error log in DirectAdmin (Logs > Error Log) to see the exact message. You can also run curl -I https://yourdomain.com to inspect the HTTP response. Always keep a backup of the original .htaccess before making changes.
What special .htaccess configuration does WordPress require?
WordPress requires a mod_rewrite block to route all requests through index.php, which WordPress Dashboard generates automatically when you save your Permalink settings. Beyond that, it is common practice to add GZIP compression to reduce file size, browser caching to improve repeat-visit speed, block access to xmlrpc.php to prevent brute-force attacks, and block PHP execution inside the /uploads/ directory to prevent malware execution.
How do multiple .htaccess files in subdirectories interact?
Apache merges .htaccess files from the document root down to the requested directory. Each file along the path is processed in order, with deeper files overriding parent files for the same directives. For WordPress, keep the root .htaccess focused on WordPress Permalink routing, and place a separate .htaccess in wp-content/uploads/ solely to block PHP execution. This separation makes maintenance easier and reduces the risk of rule conflicts.
Full-Featured DirectAdmin Hosting by AsiaGB
AsiaGB Hosting supports DirectAdmin, PHP 8.3, MySQL, and full .htaccess access. Plans start from 500 THB/year with 99% uptime.
View Hosting Plans