Apache Virtual hosts using Let's Encrypt on Ubuntu 24.04

It is no longer hard to serve multiple differently named websites from the same server with SSL. Let's Encrypt supports Apache and Ubuntu and is easy to configure. 

Success - Click the Padlock in the toolbar to see the certificate details

The Apache HTTP Server has always had a virtual hosts feature to serve multiple differently named websites from the same server. Setting it up with SSL is historically more complex than it perhaps ought to be given that the project it named after HTTP it was centred around http and https was originally an additional, often expensive and complex configuration step.

This seems outmoded to me. Even for test sites, traffic encrypted to the web browser with https is mandatory for any serious web presence. http is an insecure protocol that should really be a secondary consideration. Anyway, it is what it is.

With virtual hosts, each individual site appears to the end-user to have a different identity even though it is served from the same server. Hosting providers use this technology to provide resilience, security, scale, analytics and for many other reasons but it is also useful for developers who want to have a replica small scale development or staging environment perhaps for multiple websites. A development environment is my reason for building out Apache 2 virtual hosts with separate Let's Encrypt certificates on my Ubuntu Linux machine.

To make it work, you need to think about setting out the directory structure on your Linux machine, the Virtual Hosts configuration files for Apache2, and a properly resolving DNS record to the Linux machine hosting your virtual host site. Setting it up is easy enough but takes a little bit of concentration on the details of all these moving parts. Adding SSL to these virtual hosts is far less tricky than it once was because 'Let's Encrypt' does most of the heavy lifting now.

TL:DR — I now have two dev sites on my local Ubuntu Desktop. Both have valid Let's Encrypt SSL certificates and are working well locally and via the Internet. It took about 45 minutes to set up.

Getting ready

Theres a few things to get updated and ready before you start. You'll need :-

  • Ubuntu Desktop 24.04. LTS updated to latest. Run $ sudo apt update && sudo apt upgrade
  • Apache 2 Web Server. I'm installing Joomla so its convenient to also install the rest of the lamp stack via a task (denoted by the caret^) $ sudo apt install lamp-server^
  • An administratrive user that can 'sudo'.
  • Let's Encrypt 'certbot' installed and a good understanding of Linux locations and editing tools. To install it $ sudo snap install --classic certbotand link it $ sudo ln -s /snap/bin/certbot /usr/bin/certbot
  • An IP address on your Linux machine that is reachable from the Internet. This can either be fixed or using a dynamic DNS client for example on a laptop! You dont need this for a computer at a fixed location with a fixed IP address. $ sudo apt-get install ddclient. Read more about that here If your DNS provider is supported this is dead easy.
  • Local DNS, sometimes called 'split DNS', perfectly set up for your local area network or a hosts file with entries for the names pointing at the local IPv4 address of the Linux machine.
  • The domains you wish to use for your virtual hosts set up to use that IP address or an alias that resolves to it.

Folder structure for multiple websites

The default Ubuntu document root for Apache is /var/www/html. It contains the 'It Works' page. The documentation on this very page recommends you make your own virtual sites under /var/www.

  • I use the convention of using a public_html folder as the document root to denote content that is going to be visible on the Internet. The root level of your virtual host can then be used for private scripts and utilities that won't be visible on the Internet, but are clearly for that particular site.
  • Replace virtual_host_domain_name_one and virtual_host_domain_name_two in the following commands with the real domain names you plan to use. $ sudo mkdir -p /var/www/virtual_host_domain_name_one/public_html and then $ sudo mkdir -p /var/www/virtual_host_domain_name_two/public_html

File permissions

By default Ubuntu Apache2 uses www-data as the Apache user. This is a development machine, so the developer user needs to be able to control all the files and the Apache web server user needs to be able to serve them. I suppose you could enable the user www-data but I have my own local user.

  • Change into the webservers folder % cd /var/www
  • Make the current user the owner, but leave www-data as the group % sudo chown -R $USER:www-data /var/www/ *
  • Make sure your permissions are good. www-data needs to be able to write to install and run Joomla $ chmod +R g+w *

Create A first default page for each virtual host

Ceate a minimal HTML5 document for each index.html file. It will be replaced so this is just for testing.

<html lang="en">  
    <meta charset="utf-8">
    <title>Virtual Host</title>
    <p>Hello, world</p>

Create virtual host configuration files for Apache2

Apache uses configuration files called conf files to set our the virtual host information it needs to find the directories you created and make them available through the web server. The layout is straighforward but, i think, Ubuntu specific.

|-- apache2.conf
|       `--  ports.conf
|-- mods-enabled
|       |-- *.load
|       `-- *.conf
|-- conf-enabled
|       `-- *.conf
|-- sites-enabled
|       `-- *.conf

You can see one of mine below. It sets the name of the server, the admin email address, the document root we created on the filesystem, and places error and access logs in the servers directory above the public html directory. Then it has some directory options for security, and a rewrite rule to make all web requests use SSL. (SSL will be set up shortly using Let's Encrypt). Just replace virtual_host_domain_name_one with your domain name to use it. Create one for each host, in sites-available calling the file virtual_host_domain_name_one.conf and taking care to ensure the directories are the ones you previously created.

<VirtualHost *:80>
  ServerName virtual_host_domain_name_one
  ServerAdmin admin@virtual_host_domain_name_one
  DirectoryIndex index.html index.php 
  DocumentRoot /var/www/virtual_host_domain_name_one/public_html 
  ErrorLog /var/www/virtual_host_domain_name_one/error.log 
  CustomLog /var/www/virtual_host_domain_name_one/access.log combined 
<Directory /var/www/virtual_host_domain_name_one/public_html> 
      Options FollowSymLinks 
      AllowOverride All 
      Require all granted 

Enable the site

Once you create the conf file you need to enable it using a2ensite so that the apache web server can see it. $ sudo a2ensite virtual_host_domain_name_one does this. Check for any errors, and restart the Apache web server $ sudo systemctl restart apache2 and your virtual host is enabled. Test it by loading your Hello world homepage. Next you can apply an SSL certificate to it automatically using Lets Encrypt, but there are a couple of things to do first.

Now would be a good time to disable the default site using a2dissite as you don't really need it anymore. $ sudo a2dissite 000-default.conf does this. Do this for the default SSL site too $ sudo a2dissite default-ssl.conf.


There is a saying among the cloud technical support community that the answer to any Internet problem "is always DNS!", and it is often true. DNS resolution is the process by which your domain name is looked up and an IP address found for it (resolved). If it is not set up properly then your site won't work. If it is set up properly but your site is behind a router or firewall and the router or firewall is not set up properly it will not work. If it is set up properly using your DNS provider your site will probably still not work from your internal network but will probably work from the Internet. It seems complicated but honestly there are just a few steps to take to make sure it works reliably and well from your DNS provider and inside your home network.

  • Log in to your DNS provider
  • Set up an A record for virtual_host_domain_name_one and point it at the external IPv4 address of your router/firewall
  • Continue to the next step to set up Port Forwarding for port 443 (and Port 80), to the internal IPv4 address of your Linux machine. Nothing will work unless you can do this.

Port forwarding

Router port forwarding (This is a Ubiquiti Edge Router Lite - yours will be different and that isn't the real IP address)

Development machine /etc/hosts file

Unless you have a DNS server in your own environment you'll need to add virtual_host_domain_name_one and virtual_host_domain_name_two to your local Linux hosts file /etc/hosts so that they resolve correctly on this local machine if you want to use them say from a web browser installed locally.	localhost virtual_host_domain_name_one virtual_host_domain_name_two 

Local DNS Server

If your router has a local DNS Server capability, then you could add virtual_host_domain_name_one and virtual_host_domain_name_two to it so that it resolved exactly as it would from outside your local network on the Internet. One good way to test this is to use a mobile device on a cellular connection and make sure the site resolved with the exact same URL both from the Internet over cellular and from your local network.

Get your SSL certificates from Let's Encrypt

Assuming you've done all that and installed Let's Encrypt it is awesomely simple!

Once you are satisfied your connectivity is working then you can run the certbot tool.

  • Test everything. certbot is rate limited for production SSL certificates. The simulated enpoint accessed via --dry-run is not. $ sudo certbot certonly --dry-run -d virtual_host_domain_name_one.
  • Once the test succeeds run it for real with the --apache parameter to allow it to go ahead and install and enable your virtual host $ sudo certbot --apache -d virtual_host_domain_name_one. This is really nice and avoids typo errors in config files!

That's it. You should now be able to 'see' your site home page in a secure browser.

Success - Click the Padlock in the toolbar to see the certificate details

If it isn't working check that your domain name is really resolving to your server IP address, and check you don't have a firewall blocking access. Add an inbound firewall rule if necessary.


See also: