Nginx/Apache: set HSTS only if X-Forwarded-Proto is https

5,943

You could have multiple server blocks. So just add new server block for domains that need HSTS.

server {
    listen xx.xx.xxx.xxx:443 ssl default_server;

    # all ssl stuff
    # and other directives
}

server {
    listen xx.xx.xxx.xxx:443 ssl;
    server_name example.com other.example.com;

    # all ssl stuff
    # and other directives with HSTS enabled
}

Here first block will handle all https connections except example.com and other.example.com.

And you don't need ssl on directive if you have ssl flag in listen.

EDIT

There is another solution with only one server block:

map $scheme:$host $hsts_header {
    default "";
    https:example.com "max-age=31536000";
    https:other.example.com "max-age=31536000";
}

server {
    server_tokens off;

    listen xx.xx.xxx.xxx:80;
    listen xx.xx.xxx.xxx:443 ssl;

    ssl_certificate /etc/ssl/foo.crt;
    ssl_certificate_key /etc/ssl/private/foo.key;

    ssl_session_timeout 10m;

    # ... other ssl stuff

    location / {
        proxy_pass http://127.0.0.1:81;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header Host $host;
        add_header X-XSS-Protection "1; mode=block";
        add_header Strict-Transport-Security $hsts_header;
    }
}

We use map to define HSTS header value and use the fact, than nginx will not add header with empty value.

Share:
5,943
weeheavy
Author by

weeheavy

Fill this in sometime.... yeah

Updated on September 18, 2022

Comments

  • weeheavy
    weeheavy over 1 year

    I got the following setup:

    Internet => nginx[public:80 +443, SSL termination)
    => Varnish[localhost:81] => Apache[localhost:82]
    

    Now some sites should only be reachable via HTTPS and a valid SSL certificate. For these few exceptions I'd like to activate HSTS, either on nginx (preferred, or on Apache).

    The problem:

    • On nginx, I'd need some logic à la if Host = foo.tld then set Strict-Transport-Security xxx, but according to http://wiki.nginx.org/IfIsEvil one should not use if in a location
    • On Apache, I'd need something like if X-Forwarded-Proto 443 set Strict-Transport-Security xxx, but I don't seem to be able to construct this with SetEnvIf (Apache 2.2)

    Is my logic flawed? Another idea for an approach?

    This is the configuration currently active:

    nginx

    server {
            server_tokens off;
    
            listen xx.xx.xxx.xxx:80;
            server_name localhost;
    
            location / {
                    proxy_pass http://127.0.0.1:81;
                    proxy_set_header X-Real-IP  $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_set_header X-Forwarded-Port 80;
                    proxy_set_header Host $host;
                    add_header X-XSS-Protection "1; mode=block";
            }
    }
    
    server {
            server_tokens off;
    
            listen xx.xx.xxx.xxx:443 ssl;
            server_name localhost;
    
            ssl on;
            ssl_certificate /etc/ssl/foo.crt;
            ssl_certificate_key /etc/ssl/private/foo.key;
    
            ssl_session_timeout 10m;
    
            # http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
            ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
            ssl_prefer_server_ciphers on;
            ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
    
            location / {
                    proxy_pass http://127.0.0.1:81;
                    proxy_set_header X-Real-IP  $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_set_header X-Forwarded-Port 443;
                    proxy_set_header Host $host;
                    add_header X-XSS-Protection "1; mode=block";
            }
    }
    

    Varnish

    No special configuration.

    Apache

    <VirtualHost *:82>
    [...] nothing special
    </VirtualHost>
    
    • Alexey Ten
      Alexey Ten over 9 years
      You could always add another server block for some servers with HSTS headers
    • weeheavy
      weeheavy over 9 years
      Thanks I couldn't somehow see that option ;-) If you add it as an answer, I'll gladly accept it.
  • Gaia
    Gaia over 9 years
    Nice solution. I bet you could figure this one out too: serverfault.com/questions/630416/…
  • Nicola Gazzilli
    Nicola Gazzilli over 8 years
    The problem with HSTS is browser keeps remember if header for a domain has been sent previously. No matter if you fix that and take it out later, browser may redirect to HTTPS based on the fact HTST header was previously used.
  • Alexey Ten
    Alexey Ten over 8 years
    @Anatoly, that's not a problem but the main reason for HSTS to exist. If you want to remove HSTS, you have to set max-age=0 and wait for some period of time. Or clear your browser's cache
  • gparent
    gparent over 8 years
    This is not true. You should read paragraph 7 of the same RFC. (More precisely, it's true that it will be ignored, but it's not true that you can add it.)
  • Mark Stosberg
    Mark Stosberg over 8 years
    RFC is clear that the header must only be added over secure transport. From section 7.2 in the RFC: "An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport."