blog.thms.uk

Setting up a free Oracle VPS as a reverse proxy

Over the last few months I’ve gradually moved all my web services onto a little machine running in a closet at home.

Until now I’ve relied on Cloudflare Tunnels to let external traffic reach that home server, and deployed all my static pages to Cloudflare Pages.

In this post I’ll walk through how I set up a free Oracle VPS as a reverse proxy to replace those Cloudflare Tunnels.

I’m not quite sure what I’ll do about my Cloudflare Pages yet — I quite like the simplicity of these.

Basic architecture

Here’s the outline of what we’ll end up with:

  1. One edge server. A VPS on Oracle’s Always Free tier, though it could just as easily live with any other provider, or even be self-hosted if you have a static IP. It runs Caddy to handle reverse proxying and TLS certificates.
  2. Any number of origin servers. In my case, VMs hosted on a Proxmox box in that closet.
  3. A tailnet to connect them all. Tailscale runs on every server, and they talk to one another over their tailnet IP addresses / hostnames.

Traffic enters through the edge server, which handles TLS termination and reverse-proxies to the right host/port combination based on the hostname.

Set up the Always Free server

Oracle’s Cloud free tier is really generous: you can run Ampere ARM instances with up to 4 CPUs and 24 GB of RAM, which is plenty for an edge proxy.

The catch is twofold. The Cloud console is cumbersome to navigate, and Oracle heavily restricts the provisioning of free Ampere ARM instances on trial accounts. To get around this, I upgraded to a Pay As You Go (PAYG) account. As long as you stay within the free tier you still won’t be charged, but the provisioning restriction goes away. All you need is a credit/debit card: Oracle places a temporary $100 hold as part of the upgrade, but they don’t actually collect anything, so it stays completely free.

I also set up a billing alert to warn me if my spend ever exceeds $0.10 in a month, just in case I accidentally provision something that incurs a cost.

I won’t cover the upgrade steps in detail here; your preferred search engine or LLM will get you there.

Install Tailscale

With the bare server up, install Tailscale on it:

  1. Go to https://login.tailscale.com/admin/machines/new-linux
  2. Paste and run the generated script on the new server so it joins your existing tailnet.
  3. Optionally, enable Tailscale SSH. This lets you block SSH at the firewall so only tailnet members can reach the edge server:
    sudo tailscale set --ssh
    

You can now close port 22 on your firewall — and while you’re there, open ports 80 and 443 for HTTP(S) traffic.

Install Docker

We’ll use Docker to manage Caddy on the edge server. The built-in repository would work, but I prefer the official one, so let’s start by adding Docker’s GPG key to apt:

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

Then add the Docker repository itself:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Update the package list and install Docker:

sudo apt update
sudo apt install docker-ce

Finally, I always add the current user to the docker group so we don’t need sudo every time:

sudo usermod -aG docker ${USER}

Set up Caddy with Docker

Create a directory and change into it:

mkdir caddy
cd caddy

Create a docker-compose.yml file:

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

Create a Caddyfile:

# optionally redirect `www.` to the bare domain
www.example.com {
  redir https://example.com{uri}
}
# reverse proxy to the backend server using its Tailnet IP address (or hostname)
example.com {
  reverse_proxy 100.x.y.z
}

Since we’re on the tailnet, you can use the hostname here instead of the IP address if you prefer.

Point your DNS

If you haven’t already, point your domain at the edge server’s public IP address before continuing.

Start Caddy

docker compose up -d

That’s it. In the background, Caddy provisions a TLS certificate, and within a few seconds your new site is served through the edge server.

Adding more sites

To put another site behind the reverse proxy, just add it to your Caddyfile:

example.org {
  reverse_proxy 100.x.y.z
}

then validate and reload Caddy:

docker exec caddy caddy validate --config /etc/caddy/Caddyfile
docker exec caddy caddy reload --config /etc/caddy/Caddyfile

Conclusion

That’s a complete, free reverse-proxy setup: an Oracle Always Free edge server running Caddy, connected to your home servers over Tailscale, with automatic TLS and a one-line process for adding new sites. Compared to Cloudflare Tunnels, you get something you fully control, with no single provider sitting in front of your ingress — and it costs nothing to run.