PlayNicely - Beautiful collaboration for software developers

Multi-domain UCC SSL certificates on Nginx with 1 IP address

Posted by adam



In the past it has not been possible to serve SSL requests for multiple domains from the same IP address. So you had two choices:

  1. Use one IP address per SSL-enabled site – clearly not a great solution considering the increasing scarcity of IP addresses. Also, some cloud providers (e.g. Amazon EC2) only issue one IP address per server.
  2. Use a wildcard certificate – This can work well if you only need to secure sub-domains. The cost can also be considerably greater for this type of certificate.

But it is now also possible to use a Multi-domain UCC SSL certificate. This allows you to have a single certificate which covers up to 150 domains. The documentation for technique is still a little thin, so we wanted to share our process for getting this set up.

Before we go on, I should mention UCC stands for ‘Unified Communications Certificate’. We will be using the phrase ‘UCC certificate’ quite a lot in this blog post. Yes, it is a bit redundant, but that is just how we roll.

Generating the private key & certificate signing request

This section is largely based upon HTTPS Virtual Hosts in Apache and Creating a Certificate With Multiple Hostnames.

What sets UCC certificates apart is their use of the subject alternative names (subjectAltNames). This is a list of host names for which the certificate will be considered valid.

To generate a certificate with subjectAltNames we need to change the openssl.conf slightly. On Ubuntu, this can be found at /etc/ssl/openssl.cnf.

1. Make sure the following setting can be found in the [req] section:

[req]
req_extensions = v3_req

2. Ensure the following section exists, and that it contains the given settings:

[v3_req]
subjectAltName = @alt_names

3. List the valid host names

Now list the host names for which your certificate should be valid in a section called [alt_names]. Note that the (monetary) cost of signing this certificate will increase with the number of hostnames you specify.

[alt_names]
DNS.1 = example.com
DNS.2 = hello.example.com
DNS.3 = testing.com
DNS.4 = www.testing.com
DNS.5 = foo.testing.com

4. Generate your private key

If you do not already have a private key, then generate one now:

openssl genrsa -des3 -out ssl.key 2048

This will put your private key into ssl.key in your current directory. Note that if you specify a passphrase for the private key, you will be required to enter it every time you start/restart/reload Nginx. This may be acceptable to you, but it means it will not be possible to start Nginx automatically on system startup or after a failure.

If you choose not to specify a passphrase, then just make doubly sure to store this file safely.

5. Generate your certificate signing request

Once you have your private key, you can use it to generate your certificate signing request:

openssl req -new -key ssl.key -out ssl.csr

You will be prompted to enter a number of values. Some of these may be used by your chosen SSL provider to verify your identity. The values will also be available to people who access your secured websites.

Once done, your certificate signing request will be located in ssl.csr in your current directory.

You can check that everything looks OK by running:

openssl req -text -noout -in ssl.csr

Choosing a UCC SSL provider

There are a number of UCC SSL providers out there, each with a range of prices. As this was our first experiment with UCC certificates we decided to use GoDaddy as they seemed to be the cheapest around and we felt that there was every chance that this wasn’t going to work!

Whomever you choose, they will probably ask you to upload your ssl.csr file (or just paste the contents into a textbox).

It took a couple of hours for GoDaddy to do all of its security checks. But when it was done we were able to download our fully functional UCC SSL certificate.

Now let’s setup Nginx…

Chained certificates (optional)

It is possible that your SSL provider will issue you with a chained certificate. The reason for this boils down to security.

If this is not the case for you then you can skip this section. We used GoDaddy, and they did issue a chained certificate.

For Nginx to handle this, we just need to combine our certificate and the chained certificate into the same file (Nginx docs on the subject). All you need to do is create a file in which your domain’s certificate appears first, followed by the chained certificate.

So, on the command line:

cp mydomain.com.crt mydomain.com.combined.crt
cat chain.crt >> mydomain.com.combined.crt

For GoDaddy, the chain.crt file is called gd_bundle.crt.

It is the mydomain.com.combined.crt file that Nginx will now need.

Once again, if you do not have a chained certificate then you can ignore this step.

Setting up Nginx

The first thing you need to do is add the following to the http block of your Nginx config:

ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/cert.key;

This ensures that the SSL certificates are available to all your Nginx server blocks. You can specify these settings on a per-server basis, but doing it this way saves a little memory.

From here, there are a couple of ways to setup Nginx, you can choose which is best depending on your situation.

The easy way

You can simply add a few extra options to an existing server block.

server {
	listen 80;
	listen 443;		# This is new
	ssl on;			# This is new
 
	server_name foo.testing.com
	root /home/foo
 
	...
}

And that should do it! Just restart Nginx.

The slightly less easy way

You can also create an entirely new server block to handle HTTPS requests, so you end up with two server blocks:

server {
	listen 80;
 
	server_name foo.testing.com
	root /home/foo
 
	...
}
server {
	listen 443;
	ssl on;
 
	server_name foo.testing.com
	root /home/foo
 
	...
}

This can be useful if you need to have slightly a different configuration for your SSL site.

Serving Django via FastCGI

If you are serving Django via FastCGI then you are going to need to pass through a HTTPS parameter to the FastCGI process (Django uses this information when generating URLs for HTTP redirects). This isn’t too hard if you use two separate server blocks:

server {
	listen 80;
 
	server_name foo.testing.com
	root /home/foo
 
	...
 
	location / {
		fastcgi_pass 127.0.0.1:1234;
		fastcgi_param PATH_INFO $fastcgi_script_name;
		fastcgi_param REQUEST_METHOD $request_method;
		fastcgi_param QUERY_STRING $query_string;
		fastcgi_param CONTENT_TYPE $content_type;
		fastcgi_param CONTENT_LENGTH $content_length;
		fastcgi_param HTTPS off;	# HTTPS set to off
		...
	}
}
server {
	listen 443;
	ssl on;
 
	server_name foo.testing.com
	root /home/foo
 
	...
 
	location / {
		fastcgi_pass 127.0.0.1:1234;
		fastcgi_param PATH_INFO $fastcgi_script_name;
		fastcgi_param REQUEST_METHOD $request_method;
		fastcgi_param QUERY_STRING $query_string;
		fastcgi_param CONTENT_TYPE $content_type;
		fastcgi_param CONTENT_LENGTH $content_length;
		fastcgi_param HTTPS on;		# HTTPS set to on
		...
	}
}

Notes on browser compatibility

This section has been added to the original post following the Hacker News discussion .

Some readers have made points on browser compatibility that I think are important to highlight here:

  • It has been reported that some older Android devices have problems with UCC certificates, specifically those containing wildcard hostnames.
  • The technique detailed here is different to that of SNI, which is not supported on Windows XP.
  • As one reader pointed out, this technique is not well suited to domain lists which frequently change as registrars will probably charge for signing the updated certificate. SNI or Wildcard certificates may be more suitable here.

Please leave a comment if you have any points you would like to add to this list.

And that’s it!

You should now be able to point your browser to https://… and see your site being served securely.

Lastly, I should point out that every asset in the HTTPS version of your site will need to be addressed using an HTTPS URL, otherwise your visitors’ browsers are likely to complain.

Please let me know if you have any problems.

If you found this blog post useful then please checkout our bug tracking app, PlayNice.ly.

Update 1: There is an interesting discussion regarding browser support of this technique over at Hacker News. Please give us an up-vote if you are feeling kind :)



6 Responses to “Multi-domain UCC SSL certificates on Nginx with 1 IP address”

  1. qna says:

    Nice wrap up!
    I was trying to figure out how to configure ssl with nginx.
    Now I don’t have to worry any more.

  2. Justin says:

    Love it! thanks for the info =)

  3. Henk says:

    Hi Adam,

    I am a bit late to the party but nonetheless: great write-up! Thanks for sharing. I am hosting two Django sites on a Linode VPS and I want to enable ssl only for login to keep overall performance reasonable. I am still researching how to do that. Is it worth the trouble? How is performance if ssl is enabled for the whole site (medium traffic)? Of course, I could eat the pudding to be in the know :) but I was wondering what your experiences are with this.

  4. Name says:

    Awesomepost. By the way- I was searching on your site RSS channer or newsletter. Have You got any?

  5. adam says:

    Hi Henk!

    Thank you very much – I am pleased it was useful. The SSL overhead on the server is generally minimal (certainly in comparison to the processing needed by the web-app), but there is an overhead for the browser too. I would take a quick google around, but I found this StackOverflow post which had seemed to have a well informed answer:

    http://stackoverflow.com/questions/548029/how-much-overhead-does-ssl-impose

    But it looks like the overheads that there are can be worked around. Personally, I would enable SSL for the whole site in order to protect against session stealing.

    Best,

    Adam

Leave a Reply