Use Caddy as a reverse proxy (+ local CA!)

Self-hosting enthusiasts often run their own services such as NextCloud, Jellyfin, Home Assistant, Pihole and many others. When you start self-hosting you quickly accumulate a lot of services. Soon you will find out that accessing services by an IP address and port number is not very user-friendly, and if this sounds familiar, you've probably heard about something called a reverse proxy:

Reverse proxy flow (source: Cloudflare)

A reverse proxy is a service that sits in fronts of web services and handles all traffic towards those web services. A reverse proxy will forward access to web services based on host names: you can point the DNS records of a.mydomain.com and b.mydomain.com to the same reverse proxy and the reverse proxy will figure out which service you actually want to connect with. A reverse proxy will also allow you to configure SSL certificates so you can configure your encryption in one place.

This functionality is also extremely useful when self-hosting. Although the above image explains the situation where a reverse proxy is used for web services accessed over the internet, the same concept also applies for your local network. So let's add a reverse proxy to our own server at home!

Nginx has been widely in use as a reverse proxy for years: it's fast, mature and battle-tested. However, configuring nginx can be quite cumbersome and it wasn't necessarily designed for a world where you quickly want to be able to host new web services. Recently I came across a new modern reverse proxy (which also has many more features) that is very easy to use, Caddy. Caddy also offers automatic HTTPS which a huge benefit for hobbyists who don't want to be bothered with renewing certificates and having their services flagged as insecure.

You can easily run Caddy using Docker, below an example for running Caddy with docker-compose:

version: "3.7"
services:
  caddy:
    image: caddy/caddy
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./data/caddy/data:/data
      - ./data/caddy/config:/config
    restart: unless-stopped
    container_name: caddy

Configuring Caddy is dead simple using something called a Caddyfile, just look at this example for a reverse proxy with automatic HTTPS enabled:

mydomain.tld {
  reverse_proxy http://<SERVICE_IP>:8080
}

If you point your DNS records for mydomain.tld towards your server running Caddy it will serve whatever is running on <SERVICE_IP>:8080. If you have a service running in docker-compose, you can directly connect to the service using the service name defined in docker-compose.yml.

Adding more websites is as simple as you would hope it would be:

mydomain.tld {
  reverse_proxy http://<SERVICE_IP>:8080
}
subdomain.mydomain.tld {
  reverse_proxy http://<SERVICE_IP>:3000
}

Relaunch Caddy and that 's it, no other steps or configuration required! Both websites are now served using the same reverse proxy with automatic HTTPS.

You can even go as far as enabling HTTPS using a local certificate authority for locally hosted websites. This can be useful if you run your own DNS server and have some services internally available using hostnames such a <myservice>.lan or some other non-public TLD.

myservice.lan {
  tls internal
  reverse_proxy http://<SERVICE_IP>:8080
}

Now Caddy will generate a certificate for this website using a local certificate authority. You can fetch the root certificate from /data/caddy/pki/authorities/local/root.crt and install it on your machine so that your browser will trust your local services hosted on an internal domain. You can also install the root certificate on your iPhone if needed.

If you still have some questions, post a comment and I will try to help you. Thanks for reading!

Show Comments