I know, I know. There are other guides out there on this topic already.

The problem is none of them covered my specific use case and I like my way a little better.

As you may or may not know, CloudFlare only forwards http and https requests on specific ports. The standard guides all have you mapping one app per port. What I don’t want is a bunch of services all on different ports, and then having to remember each app’s associated port. Worse, you can run out of ports in short order. Additionally, many of the guides assume you are running a Linux gateway and ufw, or a dedicated PFSense box.

What if you’re not using those?

What if you’re using some off-the-shelf hardware, like a Ubiquiti UDM-Pro?

Well, good news! I use a UDM-Pro and better still, this method should work with any firewall and you won’t run out of ports. The only caveat is depending on the firewall you may not be able to restrict traffic to only that originating from CloudFlare.

This guide will not walk you through setting up your various services. This guide will walk you through routing your applications and getting it all working with CloudFlare.

Assumptions

Yes, I know the joke about assuming. Too bad. We’re gonna make a few and if these don’t apply, then I assume you’re reading this because you’re a masochist, like most IT people. :)

  • You have a “server”. This can be any computer that can host services, from a RaspberryPi up to a full-blown rackmount server.
  • You are going to use and have some familiarity with Linux.
  • You have some sort of firewall. Ideally something better than the “router” your ISP provided you. (If you are just using a typical home Wi-Fi router or the ISP’s router, I suggest you stop here and invest in some upgrades to your network first. Or hack your router and upgrade to OpenWRT. )
  • You have a service that you want to host. If not, the same masochist assumptions apply.
  • You have a domain name registered or plan to register one for this project.

1. Installing the Reverse Proxy.

The first step is setting up a reverse proxy. This proxy will be our application gateway to the various services you want to host. This is where your Linux host comes in. The host in question does not need to be really powerful but the more power it has, the more simultaneous SSL/TLS sessions it can handle, and the faster it can handle the encryption. The choice of Linux distribution does not really matter and is beyond the scope of this article. My personal choice is Debian, so the example commands will be Debian-specific. These commands should work the same on all Debian-based distros.

The reverse proxy we are going to use is NGINX. If you do not already have it installed on your Linux host, you can install it with the following command.

sudo apt install -y nginx

The command will install NGINX and all of the package dependencies. Once it completes, we need to set up a few settings for SSL/TLS and some virtual hosts. The virtual hosts will proxy the requests coming from the internet and relay them to the apps that you want to serve.

The first NGINX config we need to touch is the SSL/TLS settings. Depending on your distro, you may already have an ssl.conf file in /etc/nginx/conf.d. If not go ahead and create/edit the file with your favorite text editor, VIM in my case, and add the following lines.

1
2
3
4
5
6
7
8
9
#Common SSL settings
ssl_session_timeout         1d;
ssl_session_cache           shared:MozSSL:10m;  # about 40000 sessions
ssl_session_tickets         off;
ssl_dhparam                 /etc/nginx/dhparam.pem;
ssl_buffer_size             4k;

# modern configuration
ssl_protocols               TLSv1.3 TLSv1.2;

/etc/nginx/conf.d/ssl.conf


2. Setting up CloudFlare SSL/TLS.

Now that we have the baseline SSL/TLS settings configured, we need to get an SSL/TLS certificate from CloudFlare. If you haven’t already set up a CloudFlare account, go register for an account now. I’ll wait.

Welcome back! At this point, you will either need to purchase a domain or add an existing one to your CloudFlare account. If you are going to register a new one, I recommend registering it with CloudFlare as this will simplify management and CloudFlare doesn’t pad the price of the domains. The registration for the vtxtruder.com domain cost me $9.15/year. If you already have one registered through another registrar, you will need to add the site to CloudFlare and update your registrar to use CloudFlare DNS. This can be done via the “Add Site” link at the top of the CloudFlare dashboard. Just follow CloudFlare’s directions.

Now that you have set up your domain. You will need to create an Origin Server certificate. To do this select your domain from the dashboard and from the menu on the left, select SSL/TLS > Origin Server. From that screen, you will click “Create Certificate”. The default options are perfectly fine to use. Once you have created the cert, you will be shown a screen with a certificate and a private key. You will need to copy these and save the contents to a file. I recommend you store them in a directory just for your CloudFlare certificates. A command to create the directory structure is below, just replace “example.com” with the domain name you are using.

sudo mkdir -p /etc/cloudflare/example.com

The cert you copied should be pasted into a file called cert.pem, and the key should be pasted into a file called privkey.pem. Once those are saved you will go to SSL/TLS > Overview and set your SSL mode to Full or Full (strict).


3. Defining the Subdomains

Now that we have the SSL/TLS certificates stored on the server. We need to set up some subdomains. These subdomains are part of your registered domain. Each subdomain will correspond with a virtual host for each service you are providing. Let’s pretend your domain name was example.com. Now let’s pretend you were going to host the following software.

  • Home Assistant (Home automation)
  • Emby (A media server)
  • Vaultwarden (An opensource implementation of the Bitwarden API)

To handle these different pieces of software, we will create one root DNS entry and three CNAME DNS entries. The root is example.com, the CNAME entries will be as follows.

  • ha.example.com (Home Assistant)
  • media.example.com (Emby)
  • vault.example.com (Vaultwarden)

Back at your CloudFlare dashboard, you will select DNS from the menu to the left. Once there, you will click on the “Add record” button. The first record is the root record and has the following values.

Type: A
Name: @
IPv4 Address: Your ISP-provided IP address.
Proxy Status: Proxied
TTL: Auto

Note: If you already have a root A record and cannot or do not want to change it, simply define a new A record like “home.example.com”.

If you do not know your ISP provided IP address you can find it by going to IP Chicken. Now, unless your ISP has provided you a static address, this number will change from time to time. Most ISPs let you keep the same address as long as your modem is online. So before you ask, “Do I have to manually update this number every time it changes?”, no you don’t. In part 2 of this guide, we will cover how to configure automatic updates of your root domain’s IP address, aka dynamic DNS.

Now we will add the CNAME records. CNAME records are like shortcuts, they link you to another domain name. This is handy because instead of having to update every record for every hosted service, every time the IP address changes, we simply update the root record. You would then create the three CNAME records. The records are all the same except the Name field will change with each subdomain. See the examples below.

Type: CNAME
Name: ha
Target: example.com
Proxy Status: Proxied
TTL: Auto

Type: CNAME
Name: media
Target: example.com
Proxy Status: Proxied
TTL: Auto

Type: CNAME
Name: vault
Target: example.com
Proxy Status: Proxied
TTL: Auto

4. Setting up the Virtual Hosts

The moment has arrived! It’s time to proxy our services via a few virtual hosts. We will continue using the example.com domain and the three subdomains from part 3.

NGINX has a defined file structure for site configurations. They are /etc/nginx/sites-available and /etc/nginx/sites-enabled. Sites-available is where you put the config files for the various hosted sites, and sites-enabled is where you place symlinks to the config files you actually want running in NGINX. This is nice because you can add or remove sites, while still preserving their config files.

Note: The examples, below are baseline configs for the associated software. Please refer to the documentation from each software provider on the NGINX configurations specifics required. In each example you would replace the [IP ADDRESS OF YOUR…] with the actual IP address of the internal server hosting the application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
server {
        listen 8443 ssl;
        listen [::]:8443 ssl;
        server_name ha.example.com;

        ssl_certificate /etc/cloudflare/example.com/cert.pem;
        ssl_certificate_key /etc/cloudflare/example.com/privkey.pem;

        location / {
                proxy_pass http://[IP ADDRESS OF YOUR HOME ASSISTANT SERVER]:8124;
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_redirect off;
                proxy_ssl_verify off;
        }

        location /api/websocket {
                proxy_pass http://[IP ADDRESS OF YOUR HOME ASSISTANT SERVER]:8124/api/websocket;
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_redirect off;
                proxy_ssl_verify off;
        }
}

/etc/nginx/sites-available/homeassistant

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
        listen 8443 ssl;
        listen [::]:8443 ssl;
        server_name media.example.com;

        ssl_certificate             /etc/cloudflare/example.com/cert.pem;
        ssl_certificate_key         /etc/cloudflare/example.com/privkey.pem;

        location / {
                proxy_pass http://[IP ADDRESS OF YOUR EMBY SERVER]:8096;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-Proto $remote_addr;
                proxy_set_header X-Forwarded-Protocol $scheme;
                proxy_redirect off;

                # Send websocket data to the backend as well
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }
}

/etc/nginx/sites-available/emby

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name vault.example.com;

        ssl_certificate             /etc/cloudflare/example.com/cert.pem;
        ssl_certificate_key         /etc/cloudflare/example.com/privkey.pem;

        # Allow large attachments
        client_max_body_size 128M;

        location / {
                proxy_pass http://[IP ADDRESS OF YOUR VAULTWARDEND SERVER]:9090;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /notifications/hub {
                proxy_pass http://[IP ADDRESS OF YOUR VAULTWARDEN SERVER]:3012;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }

        location /notifications/hub/negotiate {
                proxy_pass http://localhost:9090;
        }
}

/etc/nginx/sites-available/vaultwarden

Once these files are created, you can link them to the sites-enabled directory with the following commands.

sudo ln -s /etc/nginx/sites-available/homeassistant /etc/nginx/sites-enabled/homeassistant
sudo ln -s /etc/nginx/sites-available/emby /etc/nginx/sites-enabled/emby
sudo ln -s /etc/nginx/sites-available/vaultwarden /etc/nginx/sites-enabled/vaultwarden

Note: If your distro provides a link to a default NGINX site, you should remove the link.

After that, you can have NGINX validate your config.

sudo nginx -t

And assuming everthing comes back ok, you can start/restart NGINX.

sudo systemctl restart nginx

6. Conclusion

Hey, you made it to the end of part 1! Congrats!

If you know how to do port forwarding, you’re ready to create a port forward to the Linux host. If that’s the case, you’re done! No need for part 2.

If on the other hand, you need to know how to handle port forwarding, firewall rules, or dynamic DNS, read on to part 2. Once I publish it. :)