For years, I struggled with a persistent yet intermittent 421 Misdirected Request error that appeared when:
- Using PHP header() redirects between domains
- Loading cross-domain resources (JS/CSS/images)
- Particularly in Safari browsers
The core issue stems from Server Name Indication (SNI) mismatches when:
1. Client requests DomainA.com
2. Server establishes TLS connection with DomainB.com's SNI
3. Browser detects SNI/Domain mismatch
4. Apache terminates connection with 421
Common problematic patterns in PHP code:
// Redirect triggering SNI mismatch
header("Location: https://static.example.com/resource");
exit;
// Resource loading vulnerable to 421
<script src="https://cdn.example.com/jquery.js"></script>
Solution 1: Separate SSL Certificates (Recommended)
# .htaccess configuration for domain-specific certs
<IfModule mod_ssl.c>
<VirtualHost *:443>
SSLCertificateFile /path/to/domain1.crt
SSLCertificateKeyFile /path/to/domain1.key
ServerName domain1.com
</VirtualHost>
<VirtualHost *:443>
SSLCertificateFile /path/to/domain2.crt
SSLCertificateKeyFile /path/to/domain2.key
ServerName domain2.com
</VirtualHost>
</IfModule>
Solution 2: HTTP/2 Connection Reuse Optimization
# Apache configuration to force proper SNI
SSLStrictSNIVHostCheck on
SSLUseStapling on
For Safari compatibility, implement these PHP checks:
function isSafariSNISensitive() {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (preg_match('/Version\/[0-9.]+.*Safari/', $ua)) {
return true;
}
return false;
}
if (isSafariSNISensitive()) {
// Serve resources from primary domain
$resourceBase = 'https://main-domain.com/static/';
} else {
$resourceBase = 'https://cdn-domain.com/';
}
Essential troubleshooting commands:
# Check active SNI configuration
openssl s_client -connect example.com:443 -servername example.com -tlsextdebug
# Verify certificate chain
curl -v https://example.com --resolve example.com:443:IP_ADDRESS
After wrestling with intermittent 421 "Misdirected Request" errors across my PHP applications for nearly two years, I finally cracked the case. This HTTP status indicates a TLS/SNI mismatch where the client (usually Safari) expects a different hostname than what the server presents during the SSL handshake.
The core issue stems from how browsers handle connection reuse with multi-domain certificates. When your Apache server serves:
// PHP redirect from domainA.com to domainB.com
header("Location: https://domainB.com/resource");
The browser might attempt to reuse the existing TLS connection, but the SNI (Server Name Indication) from the initial domainA.com handshake conflicts with domainB.com's requirements.
Modern browsers implement connection coalescing - attempting to reuse existing connections when:
- IP addresses match
- Certificate covers both hostnames
- Server supports HTTP/2
This optimization backfires when:
// Problematic flow:
1. User visits https://static.example.com/image.jpg (SNI: static.example.com)
2. App redirects to https://app.example.com/dashboard (SNI mismatch)
3. Browser reuses connection → 421 error
Option 1: Separate Certificates
The most reliable fix is using dedicated certificates per domain:
# Apache VirtualHost configuration
<VirtualHost *:443>
ServerName app.example.com
SSLCertificateFile /path/to/app_cert.pem
SSLCertificateKeyFile /path/to/app_key.pem
</VirtualHost>
<VirtualHost *:443>
ServerName static.example.com
SSLCertificateFile /path/to/static_cert.pem
SSLCertificateKeyFile /path/to/static_key.pem
</VirtualHost>
Option 2: HTTP/2 Optimization
If separate certificates aren't feasible, tweak HTTP/2 settings:
# In .htaccess or Apache config
Protocols h2 http/1.1
H2Direct off
When handling redirects between domains:
// Add connection-closing headers before redirect
header("Connection: close");
header("Location: https://newdomain.com/page");
exit();
Safari requires special handling due to aggressive connection reuse:
// Detect Safari and force full reload
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') &&
!strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome')) {
header("Cache-Control: no-store");
}
Use this curl command to test SNI handling:
curl -v -H "Host: domainB.com" --resolve domainB.com:443:IP_ADDRESS \
https://domainB.com --connect-to ::domainA.com
Key indicators of success:
- No "421 Misdirected Request" in output
- Correct certificate shown in verbose output
- Server uses proper SNI hostname