📜 ⬆️ ⬇️

How to translate an entire site to permanent HTTPS for all

Encrypt everything


The era of unencrypted web is passing, and that is good. In this manual, we assume that your server is running a Nginx web server. And now we will make sure that all site visitors use only the HTTPS protocol. In addition, we will enable HSTS - this is “HTTP Strict Transport Security”, when the site not only supports HTTPS, but also insists on using it.

There are many ways to do this, but I will describe a method called “HTTPS termination”. In other words, we will put a reverse proxy in front of the web server, which will provide HTTPS. This is easier and more flexible than configuring HTTPS using only the capabilities of a web server. You may find it counterintuitive that adding one more application to the stack will simplify your life - but this is true.

Specify that this recipe is suitable for servers based on Linux, on which Nginx is installed.
')
What will work before all the other applications in the stack is HAProxy. This is primarily a balancing application - it can distribute incoming requests between different physical servers. Many high-load sites use it in this capacity (the same reddit), but in the latest version it has the ability to perform SSL termination. He is able to establish HTTPS connections on behalf of the server.

Therefore, we set HAProxy, feed it our SSL / TLS certificates, instruct us to redirect all HTTP requests to HTTPS, and show it to the web server itself as a backend.

Install HAProxy


Ports 80 and 443 will look to the Internet and receive HTTP and HTTPS traffic. All HTTP requests will receive a 301 redirect to the same URL, but via HTTPS, and then redirected to the backend web server (nginx) via pure HTTP.

The HAProxy package included in the Ubuntu 14.04 LTS package is quite old, so we will add a repository:

sudo add-apt-repository ppa:vbernat/haproxy-1.5 


Then update the sources and install the application:

 sudo aptitude update sudo aptitude install haproxy 


The basic settings are in /etc/haproxy/haproxy.cfg. Here are my settings:

 global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin stats timeout 30s user haproxy group haproxy daemon # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private ssl-default-bind-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:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4 ssl-default-bind-options no-sslv3 no-tlsv10 tune.ssl.default-dh-param 4096 defaults log global mode http option httplog option dontlognull option forwardfor option http-server-close timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http frontend yourservername bind *:80 bind *:443 ssl crt /etc/ssl/private/cert1.pem crt /etc/ssl/private/cert2.pem acl secure dst_port eq 443 redirect scheme https if !{ ssl_fc } rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload rsprep ^Set-Cookie:\ (.*) Set-Cookie:\ \1;\ Secure if secure default_backend webservername backend webservername http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } server webservername 192.168.1.50:80 listen stats *:9999 stats enable stats uri / 


By the way, if you need syntax highlighting of the HAProxy config for vim, you can take it here . We will analyze the settings in more detail.

Global


Leave the first piece intact. These are logging settings, directory and permissions.

The following parts need to be corrected - it indicates where the CA root and SSL / TLS certificates are located. You may need to change ca-base and crt-base.

The string ssl-default-bind-ciphers determines which SSL / TLS codes will be used by HAProxy when connecting to a client. I use the recommended list from Qualys / SSL Labs. I also edited the ssl-default-bind-options line and disabled SSLv3 and TLS1.0, since they are full of holes. The last line, tune.ssl.default-dh-param, tells the program to use no more than 4096 bits in the Diffie-Hellman parameter when exchanging DHE keys.

Defaults


Add a couple of things - forwardfor and http-server-close. Since we use the application as a proxy, it should tell the server the IP addresses from which the requests come. Otherwise it will look as if all traffic comes from HAProxy. Therefore, forwardfor reports that the program works as a reverse proxy, and you need to add an X-Forwarded For header for the server.

The http-server-close configuration is needed for speed — HAProxy will decide whether to close the connection or reuse it while maintaining more advanced things like WebSockets.

Certificates


The first part of the section tells you what traffic HAproxy should handle and where to send it.

We bind HAproxy to ports 80 and 443, she listens to HTTP on port 80 and HTTPS on port 443. For HTTP, we feed her two different certificates. HAproxy uses Server Name Identification (SNI) to match the host of the incoming request with the required SSL / TLS certificate. I have three sites on the server, they use different group certificates (* .bigdinosaur.org, * .chroniclesofgeorge.com and * .bigsaur.us) and HAProxy correctly selects the one of them.

The only thing that needs to be done is to merge the certificate and private key files into one .pem:

 cat your-decrypted-ssl-key.key your-ssl-cert.crt > your-ssl-cert.pem 


Make sure that the owner of the file is root: root, and it must have read-only permissions:

 sudo chown root:root your-ssl-cert.pem chmod 400 your-ssl-cert.pem 


From HTTP to HTTPS, and HSTS as a bonus


Now we define the ACL, access control list. In HAProxy, this is a list of things that satisfy a specific criterion:

 acl secure dst_port eq 443 


We created an ACL called secure that matches everything that goes to TCP port 443. We will need it soon.

The next line is the one where the traffic is redirected from HTTP to HTTPS.

 redirect scheme https if !{ ssl_fc } 


This means that if the incoming request is not HTTPS, then you need to send a redirect 301 to the same resource, but under the HTTPS scheme.

This is exactly HTTP Strict Transport Security - all browsers are instructed to use HTTPS:

 rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload 


The setting adds the desired line to the headers. The browser recognizing this header understands that the site prefers to work on HTTPS, and this indication is valid for a year (31,536,000 seconds). The preload directive tells Googlebot that your site can be added to their list of sites that support HSTS.

HSTS is the right thing. Encryption should always be, and administrators should strive to distribute it everywhere.

By the way, it is necessary to take into account that all cookies also include the secure attribute, since from the point of view of your web server, everything happens over the usual HTTP protocol. To do this, use the directive rsprep:
one

 rsprep ^Set-Cookie:\ (.*) Set-Cookie:\ \1;\ Secure if secure 


Pay attention to "if secure". This means that only cookies that go over HTTPS are changed (secure is a variable that we defined several lines back). In general, everything should work through HTTPS, so this is not necessary in principle - but you can also make secure.

The last line determines where to send traffic. This is default_backend. Here you can define multiple servers for load sharing, etc. But since we have one backend server, everything is pretty simple.

back end


Since we have one backend server, this section is short. HAProxy only needs to add a couple of headers so that the server understands that real communication takes place via HTTPS, even if it sees only HTTP:

 http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } 


The first directive sets a header explaining that the client initially came on port 443. The second reinforces the first one by setting X-Forwarded-Proto to HTTPS if the request went through HTTPS, so that the web server understands what is happening and does not create the wrong answers.

Then we explain HAProxy, where to send requests - the name, IP and port of the web server. My web server was working on a separate machine, so I write another IP here and port 80. If everything works on one computer, write localhost and port.

Statistics, if needed


The last section dictates HAProxy to issue a status page for a given port. Here you can add basic auth by adding stats auth username: password, or define a different URL.

If you use the HSTS “includesubdomains” directive, you may not be able to query the status page by name, as the web browser will try to download its HTTPS version, and HAProxy will only give you the HTTP version. This can be bypassed by requesting a page by IP along with a port (http: //192.168.xx: 9999).

We clean


Save the settings, but do not restart the HAProxy service yet. If everything works for you on the same machine, and nginx also listens to events on ports 80 and 443, you need to tweak something.

Since nginx will no longer give out HTTPS requests, you need to remove all HTTPS references from all virtual host files.

That's all. You only need to add two lines to the main nginx.conf file to make sure that nginx will replace the ip-addresses according to the X-Forwarded-For header if the requests come from 127.0.0.1:

 set_real_ip_from 127.0.0.1; real_ip_header X-Forwarded-For; 


This will work if you compiled nginx with the ngx_http_realip_mod option.

Restart HAProxy (service haproxy restart) and she will start listening to requests.

Operation check


Make a request to the site via http. If the URL changes to https and you see the site - everything works. You can verify that the HSTS header is sent correctly:

 curl -s -D- https://yoursite.whatever/ | grep Strict 

Source: https://habr.com/ru/post/257609/


All Articles