This blog documents how I configured a new production-ready web server on DigitalOcean using their VPC and firewall tools, a custom Debian droplet, Caddy as a reverse proxy, and Tailscale for private access. I’ve done this setup a few times now, and this writeup is both a guide and a checklist for myself and others.
1. Create a new DigitalOcean project
Log in to DigitalOcean and create a new project for your site. This can help keep your account organized as you add more servers over time.
2. Configure a Firewall and tags
Go to Networking -> Firewalls. We’re going to create 2 firewalls:
- no-inbound: we’ll block everything except SSH from our IP addresses or an existing Droplet if you have one
- http-https: allow http/s through (e.g. for proxy servers)
Both firewalls will have these 2 allow rules:
- SSH (port 22): only from your Tailscale subnet or home IP
- Tailscale UDP ports (41641): optional but helpful
and then the HTTP/S one will have this rule:
- HTTP (port 80) and HTTPS (port 443): open to all
When you spin up new Droplets in the future, make sure you add them to at least 1 firewall.
2. Launch a Debian droplet in the VPC
Use the “Create Droplet” wizard:
- Choose Debian 12 x64
- Select Basic or Premium CPU depending on needs
- Attach it to the VPC created earlier
- Add your SSH key
- Enable monitoring
- Set a hostname like
web01.yourdomain
Once it’s deployed, you’ll get a public IP and private VPC IP.
4. Install and configure Tailscale
SSH into the server and install Tailscale:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
Use --advertise-tags=tag:prod
or other tags if you plan to automate ACLs. Once connected, you can disable port 22 to the public entirely and only access via Tailscale IP.
To test, connect from your computer now:
hiro@Mac:~/git/edgar-test|main ⇒ ssh caddy-01
The authenticity of host 'caddy-01 (100.xx.xx.xx)' can't be established.
ED25519 key fingerprint is SHA256:Sx<redacted>.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'caddy-01' (ED25519) to the list of known hosts.
Linux caddy-01 6.1.0-26-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.112-1 (2024-09-30) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Jul 27 21:51:28 2025 from 10.xx.xx.xx
hiro@caddy-01:~$
logout
Connection to caddy-01 closed.
5. Install Caddy and enable HTTPS
Install Caddy with systemd support:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Configure your site by editing /etc/caddy/Caddyfile
:
yourdomain.com {
reverse_proxy 127.0.0.1:3000
}
Reload the config:
sudo systemctl reload caddy
Caddy will automatically get a TLS cert from Let’s Encrypt.
6. Deploy your web app or static site
Place your app on the server and bind it to localhost:3000 (or any port you defined in the Caddyfile). Caddy will proxy incoming traffic securely and handle HTTPS for you.
7. Test and verify
- Visit your domain in a browser
- Run
curl -v https://yourdomain.com
- Check
sudo journalctl -u caddy
for any errors - SSH into the server using
tailscale ssh web01
Make sure the public IP only exposes 80/443 and that Tailscale is active.
8. Bonus: lock down or harden
If everything works:
- Remove public SSH access entirely in the firewall
- Install
fail2ban
or UFW if desired - Enable auto-updates with
unattended-upgrades
- Back up your Caddy config and site data