Johan Zietsman

Johan Zietsman

I'm Johan Zietsman, and welcome to my software blog, where I'm committed to sharing valuable insights, best practices, and in-depth tutorials.

Curious software developer, motorcycle enthusiast, rugby fanatic and an avid gamer. My code always works sometimes.



Johan Zietsman

SSL On Apache With Self Signed Certificates

How to setup SSL for multiple Virtual Hosts in Apache using self signed certificates.

SSL Certificate Basics

There are 2 essential components of a Secure Socket Layer ( SSL), the private and public key. A SSL Certificate is a form of digital identity, it contains the public key and is signed by a trusted Certificate Authority (CA) that certifies the identity (domain) represented in the certificate. Before a SSL Certificate is issued by a CA, they will go through a series of steps to verify the identity of the company applying. The private key proves ownership of a certificate and should never be shared (more on this in the next section).

Root Certificate Authorities are an alliance of companies that decide who can be trusted and they hold a set of root certificates that are used to sign other certificates. These root certificates are self signed since there is no authority to verify the identity of the root. Browsers ship with a finite list of these trusted root certificates. A Root Certificate Authority can also decide to trust another company to issue certificates, these are called Intermediate Certificates. Intermediate Certificates can sign other certificates in contrast to normal Certificates which cannot.

Self Signed Certificates are in essence certificates that are signed by an unknown and hence untrusted authority. Browsers will explicitly warn the user when connecting securely to a website that is using a Self Signed Certificate.

How does SSL Work


As the diagram shows, the private key does prove ownership of the certificate. Without the private key counterpart of the public key contained in the certificate, the server would not be able to decrypt the Symmetric Encryption Key (SEK) generated by the browser. Subsequently it would not be able to decrypt the HTTP Request that is encrypted with the SEK.

Apache, SSL and Server Name Indication (SNI)

Apache is a popular and widely used web server that can be used to host multiple sites in the form of Virtual Hosts. A name based Virtual Host is a site configuration in Apache that is activated by the name in the Host header of an HTTP Request. Apache will inspect the incoming HTTP request and try to match the Host header to the ServerName or ServerAlias of a particular Virtual Host and subsequently serve a response based on the configuration of that Virtual Host. Below is an example of 2 name based Virtual Hosts. Note that multiple virtual hosts can be configured in a single file or one per file, usually in /etc/apache2/sites-enabled/ depending on the distribution.

<VirtualHost www.monkey-app-1.local:80>
   ServerName www.monkey-app-1.local
   DocumentRoot "/var/www/www.monkey-app-1.local"
<VirtualHost www.monkey-app-2.local:80>
   ServerName www.monkey-app-2.local
   DocumentRoot "/var/www/www.monkey-app-2.local"

SSL configuration is done inside the VirtualHost configuration. The problem with this is that Apache needs to read the Host header of the incoming HTTP Request to determine which VirtualHost to use, but when the request is encrypted the Host header cannot be read until the request is decrypted. Apache will use the first VirtualHost to setup the encryption layer. This might be acceptable if Apache is configured with a wildcard certificate and all the VirtualHosts are within the same domain, but with a configuration like above the SSL limitation will become evident.

Server Name Indication or SNI is an extension to the SSL protocol that overcomes this problem by providing a way for the client (usually a browser) to include the requested Host in the initial message of the SSL handshake. SNI enables Apache to resolve the correct Virtual Host before setting up the encryption layer. SNI only works if the client supports the extension. The SSLStrictSNIVHostCheck directive can be used to force a 403 HTTP Response for clients that do not support SNI. Below is the example adapted to support SSL.

SSLStrictSNIVHostCheck on

<VirtualHost www.monkey-app-1.local:443>
  ServerName www.monkey-app-1.local
  SSLEngine on
  SSLCertificateFile      /secrets/www.monkey-app-1.local.crt
  SSLCertificateKeyFile   /secrets/www.monkey-app-1.local.key
  DocumentRoot "/var/www/www.monkey-app-1.local"

<VirtualHost www.monkey-app-2.local:443>
  ServerName www.monkey-app-2.local
  SSLEngine on
  SSLCertificateFile      /secrets/www.monkey-app-2.local.crt
  SSLCertificateKeyFile   /secrets/www.monkey-app-2.local.key
  DocumentRoot "/var/www/www.monkey-app-2.local"

Example Setup using Docker

The sample Docker project contains two sites, www.monkey-app-1.local and www.monkey-app-2.local plus the corresponding Virtual Host configuration as explained earlier in this post.

git clone
cd apache-ssl-sni

To run the sample site, first generate a private key and self signed certificate for each of the sites in a Docker volume.

docker volume create --name monkey-ssl-secrets
docker run --rm -t -i -v monkey-ssl-secrets:/secrets ubuntu /bin/bash

apt-get update
apt-get install openssl
openssl genrsa -out "/secrets/www.monkey-app-1.local.key" 2048
openssl req -new -key "/secrets/www.monkey-app-1.local.key" -out "/secrets/www.monkey-app-1.local.csr"

Country Name (2 letter code) [AU]:AU  
State or Province Name (full name) [Some-State]:QLD  
Locality Name (eg, city) []:Brisbane  
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Monkey Codes  
Organizational Unit Name (eg, section) []:  
Common Name (e.g. server FQDN or YOUR name) []:www.monkey-app-1.local.key  
Email Address []:

Please enter the following 'extra' attributes  
to be sent with your certificate request  
A challenge password []:  
An optional company name []: 

openssl x509 -req -days 3650 -in "/secrets/www.monkey-app-1.local.csr" -signkey "/secrets/www.monkey-app-1.local.csr" -out "/secrets/www.monkey-app-1.local.crt"

#repeat the openssl commands for the  2nd site too, but replace with www.monkey-app-2.local

Note that the Common Name is set to the Host name of the site. Wildcard certificates, that support multiple sub domains, can be configured using *.monkey-app-1.local as the Common Name during the openssl step that creates the Certificate Signing Request (CSR).

Now run the Docker Image and mount the volume that contains the secrets. The Docker Container also needs net-aliases to be able to resolve the 2 domain names www.monkey-app-1.local and www.monkey-app-2.local.

docker network create apache_ssl_sni

docker run --rm -p 443:443  -v $(pwd)/vhost.conf:/opt/docker/etc/httpd/vhost.conf -v $(pwd)/sites:/var/www -v monkey-ssl-secrets:/secrets --net apache_ssl_sni --net-alias www.monkey-app-1.local --net-alias www.monkey-app-2.local  webdevops/apache

The last step is to add the 2 hosts to the local /etc/hosts file so that the browser can resolve the 2 domains. On OSX the domains should point to the IP of the Docker Machine VM (docker-machine ip will show the ip).

echo $(docker-machine ip) www.monkey-app-1.local www.monkey-app-2.local >> /etc/hosts

Testing the setup

Visiting https://www.monkey-app-1.local or https://www.monkey-app-2.local in a browser should produce a warning since the certificate is not signed by a trusted authority.

Browser warning

Follow these instructions to add the self signed certificate to chrome, that will stop it from producing the above warning.

Clicking on Proceed to www.monkey-app-1.local (unsafe) will serve the sample application.


Curious software developer, motorcycle enthusiast, rugby fanatic and an avid gamer. My code always works sometimes.

View Comments