My nginx SSL Configuration with WordPress Using Domain Mapping

Introduction

If it’s not obvious I use WordPress for my (this) blog. It is a Multi-Site setup so I can use the same installation for other blogs (wife) and sites (personal/professional) I host. I also use the WordPress MU Domain Mapping plugin to assign specific domains to certain sites that are separate from the nachtimwald blogs.

For the longest time I’ve used a self signed SSL certificate to protect logins. The cert wasn’t specifically tied to a particular domain and has been expired for years. It wasn’t intended for public use but an easy way to secure logins when using public wifi. Recently Google announced that they were going to start using SSL as one of their criteria for page rank. All of my sites most likely would have been penalized for having a self signed and expired SSL certificate.

Getting an SSL Cert

The basic process for getting an SSL cert is purchasing one from a reputable company that sells them. Once purchased you’ll need to generate a CSR which includes the domain that the certificate should be issued for. The seller will verify that you are who you say you are and you are allowed to have the cert for the requested domain. That process varies depending on the seller and type of cert (extended verification cert for example).

I chose a standard / basic SSL cert because I see no need for the extended verification. The verification performed was simply having an email sent to admin@domain or to the email listed as the contact in the DNS record.

Generating a CSR

Generating the CSR is fairly easy using OpenSSL. You’ll want to generate an RSA private key of at least 2048 bits.

$ mkdir  ~/ssl
$ openssl genrsa -out ~/ssl/domain.com.key 2048
$ openssl req -new -sha256 -key ~/ssl/domain.com.key -out domain.com.csr

The “req” command will ask for information about the request such as company, and physical location.

The -sha256 is somewhat important. This will tell the seller you want a certificate signed with SHA-2. By default most sellers will sign using SHA-1 which at this time is very weak and shouldn’t be used any longer. The transition away from SHA-1 is in progress. Microsoft, Google and Mozilla have all decided to drop support for SHA-1. Microsoft set a date of January, 1 2017 for IE.

Google has decided to take a slightly more aggressive approach and will be showing a gradual decline in security via the HTTPS security indicator.

I say the -sha256 is somewhat important because some sellers have already started issuing SHA-2 certificates by default. For others you need to use that option to specifically request SHA-2.

If you don’t know if you have a SHA-1 cert you can use the handy site https://shaaaaaaaaaaaaa.com/ to check. It also has a lot more information about the SHA-1 deprecation.

Preparing the Certs

Once the CSR has been submitted and a certificate is issued I received get a bundle with the cert and intermediates that are part of the signing chain. The intermediates are important because you’ll want to have nginx return those along with the cert itself.

I received the following files:

  • AddTrustExternalCARoot.crt
  • COMODORSAAddTrustCA.crt
  • COMODORSADomainValidationSecureServerCA.crt
  • domain_com.crt

Note that you’ll want to let the SSL seller know what web sever you plan to use (if they ask for it) because web servers will want the certs in different formats. In this case (working with nginx) the certs are all in PEM format.

From this set of certs we’ll want to create two combined certs. We need one with the cert and one without (for OCSP stapling). Basically, you just append each file into one with the domain_com.crt first or not in the file at all (for OCSP stapling).

$ cat domain_com.crt > domain.pem
$ cat COMODORSAAddTrustCA.crt >> domain.pem
$ cat COMODORSADomainValidationSecureServerCA.crt >> domain.pem

$ cat COMODORSAAddTrustCA.crt > domain.stapling.pem
$ cat COMODORSADomainValidationSecureServerCA.crt >> domain.stapling.pem

Notice that the root cert (AddTrustExternalCARoot.crt) is not used at all. You should not be distributing this but relying on the client to have this already. The client should have this root cert in their root certificate store. This is how SSL certificate security is based. By having the root the client can verify the signing chain all the way down to the domain_com.crt.

Configuring nginx

The two generated pem files and the private key need to go on the server. I have all of the SSL files stored in /etc/ssl/localcerts.

Generating Larger Diffie-Hellman Parameters

By default nginx uses 1024 bit DH params. This is no longer recommended and should be increased. We can generate a larger DH to be used instead of the default by running the following:

openssl dhparam -out dhparam.pem 2048

Add the following to the server section to use the dhparam.pem:

ssl_dhparam /etc/ssl/localcerts/dhparam.pem;

Protocols and Ciphers

This parts gets tricky because there are a large number of older web browsers that do not necessarily support modern security features. IE on XP, and Java 6 for example could have issues if only strong protocols and ciphers are used. Right now I have not configured every site of mine to be HTTPS only because of these clients. They are very prevalent and while I want to switch to HTTPS only it’s hard to justify excluding those clients.

Here are the settings I’ve chosen:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!RC4:!3DES:!DSS";
ssl_prefer_server_ciphers on;

For the protocols SSLv2 is not enabled because it’s insecure. SSLv3 isn’t enabled because I want Forward Secrecy (FS or PFS) to be supported.

Additionally, TLSv1 is supported because disabling it will prevent a large number of clients from being able to connect. You might not want TLSv1 (and SSLv3) because it vulnerable to the BEAST attack. However, it is possible to have this attack mitigated client side and I’m going to rely on the client to mitigate this attack. The alternative is to enable (and use) RC4 which itself is weak and has vulnerabilities. This is a situation of which is worse, relying on the client to mitigate BEAST or using RC4… While I don’t like putting the onus on the client I think that’s better than relying on something insecure to increase security (which doesn’t make any sense).

Once the server is fully configured and up you can check the security and supported clients by using Qualys SSL test. This is a really nice tool which gives a lot of information. It was very helpful and my playing with it prompted the OpenSSL research at work I’ve posted about recently.

OCSP Stapling

It’s a good idea (and very easy) to enable OCSP stapling. We’ll use the cert bundle that does not include the domain cert for this.

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/localcerts/domain.stapling.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

The Cert Itself

Again, very easy, just set the proper values pointing to the files. We’ll want to use the cert bundle that include (and has as the first entry) the domain’s cert.

ssl_certificate /etc/ssl/localcerts/domain.crt;
ssl_certificate_key /etc/ssl/localcerts/domain.key;

Multiple Domains and SNI (Server Name Indication)

By default nginx supports SNI and there isn’t anything you need to do (no option) to enable it. SNI is the magic that allows having one Multi-Site WordPress installation with multiple domains use SSL with the same IP address.

The historic way to use SSL is to have one SSL certificate per IP address. This is still a good way to go and if you go this route you won’t have to worry about older clients that don’t support SNI. Not all clients in use today actually support SNI. Basically, clients that don’t support by the above protocols and ciphers probably don’t support SNI.

SNI is a great tool and allows multiple SSL certificates to be used with a single IP address. All that needs to be done is to have a server entry with a server_name set with the domain name for each domain.

server {
    server_name domain1.com
    ...
}

server {
    server_name domain2.com
    ...
}

Each server will have it’s own ssl_certificate and ssl_certificate_key entires pointing to their respective cert and key. For WordPress Muli-Site with Domain Mapping you’ll need a sever section for each domain you want to use SSL with and each one will be identical except for:

  • server_name
  • ssl_certificate
  • ssl_certificate_key

Realize that you can do things like have a wild card cert for domain1 and a single domain cert for domain2. You’ll just have a list of subdomains or a wild card in server_name for the domain1.

Forcing SSL (HSTS)

On some domains I have HTTPS only enforced. This consists of two pieces. Having a server entry for port 80 that redirects to https and the “real” server entry on port 443.

server {
    listen 80;
    listen [::]:80;
    server_name .domain.com;
    rewrite ^(.*) https://$server_name$1 permanent;
}

server {
    listen 443 ssl spdy;
    listen [::]:443 ssl spdy;

    add_header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
    ...
}

It’s also a good idea to set the Strict-Transport-Security header which tells the client it should interact with the server in a secure-only manner.