# Fix Apache .htaccess Rewrite Not Matching Mobile User Agents

Your Apache .htaccess file contains rewrite rules to redirect mobile users to a mobile-optimized subdomain, but mobile visitors are not being redirected. Desktop users work fine. The rules look correct but simply do not match.

The Common Problem: RewriteEngine Not Enabled

The most common reason .htaccess rewrite rules silently fail is that RewriteEngine is not enabled in the directory context:

apache
# .htaccess in document root
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} "Android|iPhone|iPad|iPod|Opera Mini|IEMobile" [NC]
RewriteRule ^$ https://m.example.com/ [R=302,L]

Without RewriteEngine On, the RewriteCond and RewriteRule directives are silently ignored. No error is logged.

The AllowOverride Problem

Even with correct rules, Apache must be configured to process .htaccess files. Check the VirtualHost or Directory configuration:

apache
<Directory /var/www/html>
    AllowOverride None  # Problem: .htaccess is completely ignored
</Directory>

Change to:

apache
<Directory /var/www/html>
    AllowOverride All
</Directory>

Or more specifically:

apache
<Directory /var/www/html>
    AllowOverride FileInfo
</Directory>

FileInfo allows RewriteEngine, RewriteCond, and RewriteRule directives. All allows everything. After changing AllowOverride, restart Apache:

bash
sudo systemctl restart apache2

Debugging Rewrite Rules

Enable rewrite logging to see exactly what Apache is matching:

apache
# Apache 2.4
LogLevel alert rewrite:trace3

Then check the error log:

bash
sudo tail -f /var/log/apache2/error.log | grep rewrite

With trace3, you will see each RewriteCond evaluation and whether it matched:

bash
[rewrite:trace3] applying pattern '^$' to uri '/'
[rewrite:trace3] RewriteCond: input='Mozilla/5.0 (Linux; Android 14; Pixel 8)' pattern='Android|iPhone|...' => matched
[rewrite:trace3] rewrite '/' -> 'https://m.example.com/'

If you see => not matched, the pattern does not match the actual User-Agent string.

Common User-Agent Matching Issues

Modern User-Agents Change Frequently

Google Chrome on Android may not always include "Android" in the User-Agent. Modern browsers use User-Agent reduction. Check actual User-Agent strings:

apache
RewriteCond %{HTTP_USER_AGENT} "Android|iPhone|iPad|iPod|Opera Mini|IEMobile|Mobile.*Safari|Mobile.*Chrome" [NC]
RewriteCond %{HTTP:X-Device-Type} "mobile" [NC,OR]
RewriteCond %{HTTP:Sec-CH-UA-Mobile} "?1"

The Sec-CH-UA-Mobile header is part of the new Client Hints API and is more reliable than User-Agent sniffing.

Checking Query String Exceptions

Mobile users who manually want the desktop site often add ?m=0 or ?desktop=1. Add an exclusion:

apache
RewriteCond %{QUERY_STRING} !(^|&)desktop=1 [NC]
RewriteCond %{HTTP_USER_AGENT} "Android|iPhone|iPad|iPod|Opera Mini|IEMobile|Mobile.*Safari" [NC]
RewriteRule ^$ https://m.example.com/ [R=302,L]

Cookie-Based Override

Allow users to override the redirect:

apache
RewriteCond %{HTTP_COOKIE} !^.*prefer_desktop=1.*$ [NC]
RewriteCond %{HTTP_USER_AGENT} "Android|iPhone|iPad|iPod|Opera Mini|IEMobile|Mobile.*Safari" [NC]
RewriteCond %{QUERY_STRING} !(^|&)desktop=1 [NC]
RewriteRule ^$ https://m.example.com/ [R=302,L]

Users who set a prefer_desktop=1 cookie will always get the desktop site.

Testing the Rules

```bash # Test with Android User-Agent curl -sI -A "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 Chrome/120.0.0.0 Mobile Safari/537.36" https://example.com/ | grep -E "HTTP/|Location"

# Test with iPhone User-Agent curl -sI -A "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15" https://example.com/ | grep -E "HTTP/|Location"

# Test with desktop User-Agent (should NOT redirect) curl -sI -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" https://example.com/ | grep -E "HTTP/|Location" ```

The first two should return a 302 redirect to https://m.example.com/. The third should return 200 with no redirect.