Raph's Blog

How to deploy an Apache web server to the cloud

It’s one of those basic things I never had to do myself in my career until I started a personal project. I didn’t really find a good end-to-end explanation on how do this online, so I thought I would try. I’ll be taking examples from my project, github.com/raphael-p/beango-messenger.

I used a cloud-hosted VM to do this, because it seemed like the simplest way. If you want to just host a static website instead of your own web server, there’s plenty of out-of-the-box solutions, including free ones. Alternatively, you can self-host, here’s a good article for that: blog.prutser.net/2021/01/20/how-to-securely-self-host-a-website-or-web-app/.

Build a web server locally

This probably goes without saying, but you should start by doing that. Don’t worry about HTTPS yet either.

Dockerise you web server

This is not strictly necessary, but it does make deploying a lot easier.

First you’ll need a Dockerfile, like this one: github.com/raphael-p/beango-messenger/blob/master/Dockerfile. This contains the commands used to assemble the main container.

In my case, I also needed a database and to do some config, so I also created a docker-compose file: github.com/raphael-p/beango-messenger/blob/master/docker-compose.yml. This defines an app with multiple containers. The Dockerfile is referenced in there: build: ..

Docker containers are managed from the command line, the commands to get you started are docker compose up (with --build flag) and docker compose down (could also be docker-compose depending on version).

Acquire a web domain from a registrar

A web domain name is a domain + a top-level domain, e.g. “mywebsite.com”. You can find a domain registrar by searching “website domain”. They’ll try to sell you extras you probably don’t need, ignore those. Also, the same domain name can be priced differently across vendors, so compare if you feel like it.

Get a Linux virtual machine (VM) running

This could be hosted on your own laptop, but using a cloud provider is a good idea if you want it running 24/7. I went with DigitalOcean (use my referral link if you like: m.do.co/c/90150ae925f0, you’ll get free credit). It costs me around 6 USD per month, although my traffic is relatively low.

Here’s DigitalOcean’s instructions on how to get started docs.digitalocean.com/products/droplets/how-to/create/. I picked an Ubuntu VM with Docker preinstalled for convenience.

Whichever provider you decide to go with, just make sure you can SSH into it from your command line.

Deploy your web server to the VM

You can get it there by either moving the files over SSH, or cloning your GitHub repo (my preferred approach). If you go for the latter, I would recommend setting up read-only API key(s) for the specific repo(s) you want to clone and putting a password on them.

Once it’s running, your web server should be accessible over the internet using the VM’s IP address and the port it’s running on (should be specified in the web server config). So for example, if you type in 123.45.678.91:8080/home into your browser it should return your web server’s home page.

Get your website to route to your VM

You’ll need to update the nameservers in your domain’s DNS settings, which you’ll find on your domain registrar’s website. Your cloud provider should tell you what its nameservers are somewhere in the settings. Here’s the instructions for DigitalOcean: docs.digitalocean.com/products/networking/dns/getting-started/dns-registrars/. Keep in mind DNS changes can take a while to propagate globally.

If you’re self-hosting, you’ll need to create an A-record pointing directly to your VMs IP.

A nameserver is like a mini-DNS, so you’ll still need to configure the DNS records within your cloud provider to point to your VM. There should be fairly straightforward instructions to do that. The end result should be an A-record with your domain name as the hostname and the VM’s IP as its value. Note that you’ll need an A-record for each subdomain, so in my case I had one for “www.beango.co.uk” and one for “beango.co.uk”.

If you’ve done this correctly, you can now replace your IP address with http://mydomain.com/home to open your home page.

Get a reverse-proxy running on your VM

This is where you can start thinking about HTTPS. A reverse proxy will allow you to handle and authenticate all incoming HTTP(S) requests, and point them to the port.

I used Apache’s HTTP server, because I was already familiar with it, but nginx is also a solid choice. Here’s some install instructions for Linux: digitalocean.com/community/tutorials/how-to-install-the-apache-web-server-on-ubuntu-20-04. I skipped step 5 since i wanted to set up my config a bit differently…

In /etc/apache2/apache2.conf, I allowed access to my web server’s client resource file (html, css, js, icons) by adding this directive:


<Directory /root/repos/beango-messenger/client/resources/>
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>
        

And in /etc/apache2/sites-available/, I created beango.co.uk.conf (beango.co.uk is my domain name):


<VirtualHost *:80>
    ServerAdmin raphael.piccolin@outlook.com
    ServerName beango.co.uk
    ServerAlias www.beango.co.uk
    # uncomment this line once you have HTTPS set up
    # Redirect permanent / https://beango.co.uk/
</VirtualHost>
        

Once you restart your Apache server (the commands are in the link above), you should be able to access your web server from a browser using its domain name, but only with HTTP (unsecured).

Enable TLS/SSL on the HTTP server

For this part, you’ll need to create a certificate which is signed by a certificate authority (CA). Let’s Encrypt makes this really easy since they provide a certbot, which will also update your HTTP server configuration.

Just follow this tutorial: digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-apache-in-ubuntu-20-04.

After doing this, I also added this snippet into /etc/apache2/apache2.conf, which redirects all HTTP (port 80) requests to HTTPS (port 443):


ServerName localhost

<VirtualHost *:80>
    ServerName 138.68.137.66
    RewriteEngine On
    RewriteRule ^ https://%{HTTP_HOST} [L,R=301]
</VirtualHost>
        

A new Apache config file should have been auto-generated in /etc/apache2/sites-available/. It creates a virtual host on port 443 for requests against your domain, which will handle all incoming HTTPS requests. Mine was called beango.co.uk-le-ssl.conf. I made some changes to it, such as:

Here’s the full file:


<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerAdmin raphael.piccolin@outlook.com
    ServerName beango.co.uk
    ServerAlias www.beango.co.uk
    DocumentRoot /root/repos/beango-messenger
    ProxyPreserveHost On
    ProxyPass / http://localhost:8081/
    ProxyPassReverse / http://localhost:8081/

    Include /etc/letsencrypt/options-ssl-apache.conf
    SSLCertificateFile /etc/letsencrypt/live/beango.co.uk/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/beango.co.uk/privkey.pem

    RewriteEngine On
    RewriteCond %{SERVER_NAME} =www.beango.co.uk [OR]
    RewriteCond %{SERVER_NAME} =138.68.137.66
    RewriteRule ^ https://beango.co.uk%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
</IfModule>
        

Add a firewall

For security reasons, you should restrict outside access to your VM to only ports 80 (HTTP), 443 (HTTPS), 22 (SSH), and 2375 + 2376 (used by Docker).

Be careful not to lock yourself out of your VM by firewalling the SSH port!

This is necessary for security, since otherwise people can access your web server’s port (or any other service) directly and bypass HTTPS. All requests for port 80 and 443 will got through you HTTP server. If it’s configured correctly, all HTTP requests will be redirected to 443, ensuring secure connections.

I used ufw to configure my firewall, see instructions here: ubuntu.com/server/docs/security-firewall. It comes preinstalled on Ubuntu, it’s a wrapper around iptables which makes it easier to use.

My firewall looks like this:

> sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     LIMIT       Anywhere                  
2375/tcp                   ALLOW       Anywhere                  
2376/tcp                   ALLOW       Anywhere                  
Apache Full                ALLOW       Anywhere                  
80/tcp                     ALLOW       Anywhere                  
443                        ALLOW       Anywhere                  
22/tcp (v6)                LIMIT       Anywhere (v6)             
2375/tcp (v6)              ALLOW       Anywhere (v6)             
2376/tcp (v6)              ALLOW       Anywhere (v6)             
Apache Full (v6)           ALLOW       Anywhere (v6)             
80/tcp (v6)                ALLOW       Anywhere (v6)             
443 (v6)                   ALLOW       Anywhere (v6) 
        

Firewall: the Docker problem

By default, Docker will bypass ufw rules and allow its ports to be accessed externally. Thankfully, someone’s solved this: github.com/chaifeng/ufw-docker. I pasted the snippet from there into /etc/ufw/after.rules and restarted ufw. Initially that didn’t work, but all I had to do was comment out the line with *filter.

That’s all, thanks ✨