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>
  • 2
    You could always add another server block for some servers with HSTS headers – Alexey Ten Sep 23 '14 at 5:50
  • Thanks I couldn't somehow see that option ;-) If you add it as an answer, I'll gladly accept it. – weeheavy Sep 23 '14 at 17:42
up vote 4 down vote accepted

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.

  • Even a better solution, thanks very much. – weeheavy Sep 25 '14 at 17:57
  • Nice solution. I bet you could figure this one out too: serverfault.com/questions/630416/… – Gaia Oct 6 '14 at 1:09
  • 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. – Anatoly Sep 11 '15 at 7:50
  • 2
    @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 – Alexey Ten Sep 11 '15 at 8:43

You can also add the HSTS header unconditionnaly.

Indeed, it only takes effect if the connection succeeded, over TLS and without cert warning. Cf paragraph 5.1 of RFC6797.

  • 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.) – gparent Oct 15 '15 at 15:18
  • 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." – Mark Stosberg Dec 18 '15 at 21:47

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.