Running Caddy as a reverse proxy on CentOS 7

Caddy is a relatively new and easy-to-use web server written in Go. It has a few notable features, including being able to automatically request and renew free SSL certificates from Let’s Encrypt.

In my environment, I have multiple web/application servers, none of which are directly accessible from the internet, and I have one Internet-facing server running Caddy. I am using Caddy to achieve the following requirements:

  • Host multiple web sites/applications on a single public IP address.
  • Secure Internet traffic to my web/application servers using SSL certificates.
  • Perform TLS termination on the Internet-facing Caddy server to simplify administration of SSL certificates. In other words, I don’t have to manage certificates on multiple servers.

Some of the configurations in this guide were taken from David Leonard’s blog, especially getting Caddy to run as a service. Many thanks to David for sharing his config!

CentOS Preparation

For this walk-through, I built a VM in VMware ESXi 6.0 with 1 GB RAM and 1 vCPU, and I chose to perform a “minimal” installation of CentOS 7. The CentOS installation is relatively self explanatory, so I will forgo talking about that part. However, I do recommend that you configure your instance of CentOS with either a static IP address or a DHCP reservation, which will help in creating port-forwarding rules on your router.

Once CentOS is installed, we need to install open-vm-tools and apply system updates via yum. Please note that if you are not using VMware, you do not need to run the first command.

yum install -y open-vm-tools
yum update -y
reboot

For security reasons, we are going to create a service account without root privileges for caddy. We’ll name the account caddy.

adduser caddy -s /sbin/nologin

Caddy Installation

For the actual installation of Caddy, you will need to get the URL for the latest version directly from the Caddy download page. We don’t need any of the additional features, but we do need the Linux 64-bit version.

In these commands, we will download the latest version of Caddy and extract the archive to a temporary directory.

curl -L -O https://caddyserver.com/download/builds/162307125800956/caddy_linux_amd64_custom.tar.gz
mkdir caddy
tar zxvf caddy_linux_amd64_custom.tar.gz -C ./caddy

Next, we will move the caddy binary into place and create a directory for logging.

mkdir /opt/caddy
cp ./caddy/caddy /opt/caddy
chown -R caddy:caddy /opt/caddy
mkdir /var/log/caddy
chown caddy:caddy /var/log/caddy

At this point, the original archive and extracted files can be safely deleted.

rm -f caddy_linux_amd64_custom.tar.gz
rm -rf caddy

Normally, a process must have root privileges to bind to ports 80 and 443. This command will allow caddy to bind to these ports without root privileges.

setcap cap_net_bind_service=+ep /opt/caddy/caddy

Caddy Configuration

After installing Caddy, we need to create the configuration file using a text editor such as vi.

vi /opt/caddy/prod.caddy

There are multiple # Comments in the configuration file to show what I am doing, but for more options, please refer to the Caddy documentation. The contents of /opt/caddy/prod.caddy are listed below:

# Get rid of 'www' for the main web site
www.example.com {
    # Redirect to the main web site, keeping the full URL (accomplished with '{uri}')
    redir https://example.com{uri}
}

# The main web site, without 'www'
example.com {
    # Enable compression for files that are not compressed (e.g. compress .html but not .jpg)
    gzip
    # Reverse proxy all requests ( '/' ) to the backend server ( 'web01.internal.example.com' )
    proxy / web01.internal.example.com {
        # Tell the backend web server which protocol was used to connect to the Caddy server
        proxy_header X-Forwarded-Proto {scheme}
        # Tell the backend web server the IP address of the client
        proxy_header X-Forwarded-For {remote}
        # Tell the backend web server the hostname requested by the client
        proxy_header Host {host}
    }
}

# Another application
app.example.com {
    gzip
    # On the backend server, this application runs on a port other than 80
    proxy / app01.internal.example.com:8090 {
        proxy_header X-Forwarded-Proto {scheme}
        proxy_header X-Forwarded-For {remote}
        proxy_header Host {host}
    }
}

Running Caddy as a Service

By default, Caddy runs from the command line. This means that it will not start up automatically after a system reboot. We will create a systemd service to allow caddy to run when the system starts up.

First, create a file for the caddy service.

vi /etc/systemd/system/caddy.service

The contents of /etc/systemd/system/caddy.service are listed below:

; see `man systemd.unit` for configuration details
; the man section also explains *specifiers* `%x`

[Unit]
Description=Caddy HTTP/2 web server
Documentation=https://caddyserver.com/docs
After=network-online.target
Wants=network-online.target
Wants=systemd-networkd-wait-online.service

[Service]
; run user for caddy
User=caddy
ExecStart=/opt/caddy/caddy -agree=true -conf=/opt/caddy/prod.caddy
Restart=on-failure
; create a private temp folder that is not shared with other processes
PrivateTmp=true
; limit the number of file descriptors, see `man systemd.exec` for more limit settings
LimitNOFILE=8192

[Install]
WantedBy=multi-user.target

Now, we will allow the caddy service to run when the system reboots.

systemctl enable caddy

If you want to start caddy without rebooting, you can run this command.

systemctl start caddy

Finally, this command will show whether caddy is running or not.

systemctl status caddy

The output will look something like this when caddy is running.

● caddy.service - Caddy HTTP/2 web server
   Loaded: loaded (/etc/systemd/system/caddy.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2016-07-30 18:00:00 CDT; 1h 43min ago
     Docs: https://caddyserver.com/docs
 Main PID: 861 (caddy)
   CGroup: /system.slice/caddy.service
           └─861 /opt/caddy/caddy -agree=true -conf=/opt/caddy/prod.caddy

Summary

We now have a reverse proxy server that automatically generates and maintains SSL certificates. In the example configuration, users who navigate to http://www.example.com/about are automatically redirected to https://example.com/about, and the web application is accessible at https://app.example.com. Unless explicitly told not to, Caddy will redirect all unencrypted HTTP requests to secure HTTPS requests. And all of that for free!