How to Pass SSL/TLS Protocol Information from Nginx to Backend via HTTP Headers


3 views

Following the POODLE vulnerability disclosure, many teams need to transition away from SSLv3 while maintaining backward compatibility during the migration period. Our specific requirement was to:

  • Detect client SSL/TLS protocol version at the nginx level
  • Pass this information to backend applications
  • Display warnings to users still using SSLv3

Nginx provides built-in variables that expose SSL protocol information. Here's how to configure the proxy header:

server {
    listen 443 ssl;
    ...
    
    # Pass SSL protocol to backend
    proxy_set_header X-SSL-Protocol $ssl_protocol;
    proxy_set_header X-SSL-Cipher $ssl_cipher;
    
    location / {
        proxy_pass http://backend;
    }
}

While this approach works for warning users, it's crucial to understand that:

  • The TLS handshake completes before HTTP headers are sent
  • Sensitive data may already be transmitted before your backend can react
  • This should only be used as a temporary measure during migration

Here's a full working example with protocol-based routing:

map $ssl_protocol $ssl_warning {
    "TLSv1.3"   "";
    "TLSv1.2"   "";
    default     "1";
}

server {
    listen 443 ssl;
    server_name example.com;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    
    proxy_set_header X-SSL-Protocol $ssl_protocol;
    proxy_set_header X-SSL-Warning $ssl_warning;
    
    location / {
        if ($ssl_warning) {
            add_header X-SSL-Warning "Deprecated protocol detected: $ssl_protocol";
        }
        proxy_pass http://backend;
    }
}

For production systems, consider immediately rejecting SSLv3:

server {
    listen 443 ssl;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384...';
    
    # Return error for old protocols
    error_page 495 = @ssl_reject;
    if ($ssl_protocol = "SSLv3") {
        return 495;
    }
    
    location @ssl_reject {
        return 403 "SSLv3 not supported. Please upgrade your client.";
    }
}

Here's how to handle the protocol information in various backend languages:

PHP Example:

$protocol = $_SERVER['HTTP_X_SSL_PROTOCOL'] ?? '';
if (strpos($protocol, 'SSLv3') !== false) {
    header('X-SSL-Warning: Your client is using deprecated SSLv3');
}

Node.js Example:

app.use((req, res, next) => {
    const protocol = req.headers['x-ssl-protocol'];
    if (protocol && protocol.includes('SSLv3')) {
        res.set('X-SSL-Warning', 'Deprecated protocol detected');
    }
    next();
});

Following the POODLE vulnerability disclosure, many teams face the transitional challenge of moving away from SSLv3 while maintaining user communication. The core technical hurdle lies in detecting the protocol version before any sensitive data transmission occurs.

Nginx provides access to SSL protocol information through built-in variables. Here's how to extract and forward this data:

server {
    listen 443 ssl;
    # ... other SSL configs ...
    
    location / {
        proxy_set_header X-SSL-Protocol $ssl_protocol;
        proxy_set_header X-SSL-Cipher $ssl_cipher;
        proxy_pass http://backend;
    }
}

The backend can then check the forwarded headers:



    Header set X-SSL-Warning "Deprecated protocol detected: SSLv3"

As correctly identified in the original post, this approach has a critical timing issue:

  • The TLS handshake completes before HTTP communication begins
  • Clients may transmit sensitive data in their first request
  • The warning header arrives too late to prevent initial data exposure

A more robust solution would combine immediate protocol rejection with user notification:

# In nginx.conf
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

map $ssl_protocol $ssl_blocked {
    "SSLv3" 1;
    default 0;
}

server {
    listen 443 ssl;
    
    if ($ssl_blocked) {
        return 444; # Special nginx non-response
    }
    
    error_page 444 /sslv3_warning.html;
    location = /sslv3_warning.html {
        root /var/www/errors;
        internal;
    }
    
    # ... normal proxy configuration ...
}

When implementing these changes, consider:

  • Legacy browser support matrices
  • Mobile device TLS capabilities
  • API client compatibility requirements

Implement phased monitoring before full enforcement:

log_format ssl_monitoring '$remote_addr - $ssl_protocol/$ssl_cipher';
server {
    listen 443 ssl;
    access_log /var/log/nginx/ssl_protocols.log ssl_monitoring;
    # ... other configs ...
}