Troubleshooting HTTP/2 Not Working in Nginx Despite Proper Configuration


1 views

Recently while upgrading to Nginx 1.9.6 on Ubuntu 14.04.2 LTS, I encountered a puzzling situation where HTTP/2 wasn't being served despite having all the correct configurations in place. Let's dive into the investigation and solution.

First, let's verify the essential components for HTTP/2 support:

# Verify Nginx version and modules
nginx -V | grep http_v2

# Sample working http2 configuration
server {
    listen 443 ssl http2;
    server_name example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ...
}

Several factors could prevent HTTP/2 from working:

  • SSL certificate issues (self-signed certs might cause fallback)
  • Outdated OpenSSL version
  • Browser caching or forced protocols
  • Missing ALPN support

Use these commands to verify HTTP/2 support:

# Check protocol with curl
curl -I --http2 https://yourdomain.com

# Alternatively with openssl
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -nextprotoneg ''

Based on the configuration shown, here are potential fixes:

# 1. Ensure modern cipher suites
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384...';

# 2. Enable ALPN explicitly
ssl_prefer_server_ciphers on;

# 3. Verify system OpenSSL version
openssl version

Here's a complete configuration that worked for me:

server {
    listen 443 ssl http2;
    server_name yourdomain.com;
    
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384...';
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Other server directives...
}

When facing HTTP/2 issues with Nginx, first verify if the module is properly loaded:

nginx -V 2>&1 | grep -o with-http_v2_module

Your output shows the module is included in the build, which confirms basic compatibility.

HTTP/2 has strict TLS requirements that differ from HTTP/1.1:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256...';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

Modern browsers require specific conditions for HTTP/2:

# Check Chrome's protocol through Developer Tools:
1. Open Chrome DevTools (F12)
2. Navigate to Network tab
3. Right-click header columns and enable "Protocol"
4. Reload page and verify protocol

Here's a verified HTTP/2 configuration that works with Chrome:

server {
    listen 443 ssl http2;
    server_name example.com;

    # Modern TLS configuration
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256...;
    ssl_prefer_server_ciphers on;

    # HTTP/2 optimization
    http2_push_preload on;
    http2_max_concurrent_streams 100;

    # Root configuration
    root /var/www/html;
    index index.html;
}

1. Self-signed certificates often prevent HTTP/2 negotiation - use Let's Encrypt instead
2. Outdated OpenSSL versions may lack ALPN support - upgrade to 1.0.2+
3. Mixed content (HTTP resources on HTTPS page) can cause fallback
4. Old Nginx versions may need explicit http2 in listen directive

Use these commands to test HTTP/2 support:

curl -I --http2 https://yoursite.com
openssl s_client -connect yoursite.com:443 -nextprotoneg ''