← Back to Blog

Reverse Proxy and Free HTTPS on a VPS: Caddy vs Nginx vs Traefik

Published · by RS Computers

Reverse proxy HTTPS VPS

You have one VPS, one IPv4 address, a few apps on ports like 3000 and 8080, and a browser that calls each of them "Not secure". The fix is a reverse proxy with Let's Encrypt: one program that owns ports 80 and 443, reads which hostname each visitor wants, holds a free certificate for every name and passes the request to the right app on a local port. Use Caddy unless you have a reason not to. Nginx with Certbot suits servers already running Nginx; Traefik suits all-Docker setups.

Quick version:

Caddy vs Nginx vs Traefik: which one should you run?

All three serve many sites from one IP thanks to SNI (Server Name Indication), the TLS extension that lets the browser name the host it wants before the server picks a certificate. They differ in how much you write and what you bolt on.

FeatureCaddyNginx + CertbotTraefik
CertificatesBuilt in, ZeroSSL as fallbackCertbot obtains them and edits your configBuilt in, per router
One more site costsThree linesA 15-line file and a Certbot runFour container labels
WebSocketsAutomaticExtra headers; idle sockets close after 60 sAutomatic
HTTP/3On by defaultExperimental, manualOne flag
Rate limitingThird-party module, custom buildBuilt inBuilt in

Our pick is Caddy. A working HTTPS site takes three lines of config, and its defaults are the ones you would choose anyway. Choose Nginx if you already run it or need rate limiting today; it is wordy, but every decision is visible. Choose Traefik when containers come and go, and accept that it reads the Docker socket, which amounts to root on the host. NGINX's own ACME module, which skips Certbot, has grown up since its August 2025 preview: version 0.4 (April 2026) added ARI and ships as nginx-module-acme in nginx.org's repository. Debian does not package it and it still has no DNS-01, so no wildcards; on a stock Debian server Certbot remains the simpler route.

Why run the proxy on your own server?

Hosted front doors such as Cloudflare cap upload size and rate-limit rules per plan, and they see your traffic decrypted. On your own VPS those limits are lines in your config and the private keys stay on your disk. Ending TLS in Amsterdam or Dublin also keeps decrypted traffic and logs in the EU. Rotate those logs too, because the EU Court of Justice ruled in Breyer (2016) that even dynamic IP addresses can be personal data for a website operator.

How a reverse proxy with Let's Encrypt gets its certificates

Let's Encrypt is a free, automated certificate authority run by the nonprofit Internet Security Research Group (ISRG). It speaks ACME, the protocol a client uses to request a certificate and prove it controls the name. The Let's Encrypt challenge types page describes three kinds of proof still in use:

Validation comes from unpublished addresses, so never geo-block port 80. And with an AAAA record present, Let's Encrypt connects over IPv6 first and falls back to IPv4 only when that connection fails outright, so an AAAA record that points at another server fails validation.

Lifetimes are shrinking

Certificates last 90 days by default. Let's Encrypt is moving to 45 days: its opt-in tlsserver profile has issued 45-day certificates since 13 May 2026, the default drops to 64 days on 10 February 2027 and to 45 days on 16 February 2028. CA/Browser Forum ballot SC-081v3 caps every public TLS certificate at 47 days by March 2029, and its first step already applies: since 15 March 2026 no public certificate may last longer than 200 days, so a paid certificate is no way around renewals either. Renew at about two-thirds of the lifetime, as Certbot 4.0 does, or use ARI (ACME Renewal Information, where the CA tells the client when to renew), as Caddy and Certbot 4.1 or newer do; a hardcoded 60-day cron job will not survive. Also, expiry reminder emails ended in June 2025, and OCSP that August, so ssl_stapling on; in old configs is dead weight.

Rate limits and staging

The Let's Encrypt rate limits bite in testing: 50 certificates per registered domain per 7 days, but only 5 for the exact same set of hostnames, with one slot back every 34 hours and no override. Rebuild five times in an afternoon and the sixth attempt fails. Renewals that follow ARI are exempt from all of these limits; first issuance is not. Test with Certbot's --staging flag, or point Caddy's acme_ca or Traefik's caServer at the staging directory.

Which VPS plan fits a reverse proxy and its apps?

The proxy is the small part; the apps behind it set the size. None of the three projects publishes a memory minimum, and with this guide's example sites running on our own 8 vCPU test machine (16 GB of RAM), Nginx used about 17 MB of RAM across its master and eight worker processes, Caddy about 50 MB and Traefik about 100 MB. Debian's installation guide recommends 1 GB of RAM for Debian 13 without a desktop.

Because SNI lets one address carry every site, a single IPv4 address is enough. RS Computers gives every VPS and VDS its own IPv4 and IPv6 address, so the A and AAAA records from step 1 point at addresses no other customer shares. The servers are KVM virtual machines on NVMe storage in Amsterdam (Netherlands), Prishtina (Kosovo) and Dublin (Ireland), and traffic through the proxy is unmetered on a 1 Gb/s port (VPS) or a 10 Gb/s port (VDS). Which of the three cities can take a new server today is shown on the plans page. You choose Debian 13 when you order, and the client area is where you restore the free weekly backup or move to a bigger plan.

PlanHandles
VPS Nano (1 vCPU, 1 GB, 20 GB NVMe, 1 Gb/s)Caddy or Nginx for a few static sites or one small app. No Docker.
VPS Micro (2 vCPU, 2 GB, 40 GB NVMe, 1 Gb/s)The proxy plus two or three light apps, such as a Node service and WordPress.
VPS Mini (4 vCPU, 4 GB, 80 GB NVMe, 1 Gb/s)A Docker host with Traefik in front of a handful of self-hosted tools.
VDS Small (4 vCPU, 8 GB, 240 GB NVMe, 10 Gb/s)Production sites with databases, and file-heavy apps that use the 10 Gb/s port.
VDS Medium (8 vCPU, 16 GB, 480 GB NVMe, 10 Gb/s)An agency server: many client domains, each with its own certificate.
VDS Large (16 vCPU, 32 GB, 960 GB NVMe, 10 Gb/s)Busy shops, SaaS-style apps, or several heavy stacks on one machine.

Get DNS and ports right before any proxy

Commands run as root on fresh Debian 13; as a normal user, put sudo in front. New to SSH and ufw? Our 30-day plan for learning Linux on a VPS covers both in its first ten days.

  1. Point the names at the server: A records for YOUR_DOMAIN, www and app with the IPv4 address, AAAA records with the IPv6 address (or none at all). Install dig and check what the rest of the world sees:

    # as root
    apt update
    apt install -y dnsutils
    dig +short A YOUR_DOMAIN
    dig +short AAAA YOUR_DOMAIN
    dig +short A www.YOUR_DOMAIN
    dig +short A app.YOUR_DOMAIN

    Each output line should be your server's address. Empty output means the record is missing or still spreading, so wait before requesting certificates.

  2. Open the firewall; Debian 13 has none active. If SSH listens on a port other than 22, change that line first. Allow SSH, HTTP, HTTPS and UDP 443 (for HTTP/3), then switch ufw on:

    # as root
    apt install -y ufw
    ufw allow 22/tcp
    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw allow 443/udp
    ufw --force enable
    ufw status verbose

    You should see Status: active and an ALLOW IN line for each port, plus (v6) copies.

  3. Find each app's address by listing every listening TCP socket and the program that owns it:

    # as root
    ss -tlnp

    A line like 127.0.0.1:3000 with users:(("node",...)) is ideal: the proxy can reach the app, the internet cannot. Use that exact address; localhost can resolve to the IPv6 ::1.

The same two sites on Caddy and on Nginx

In the example below, a shop front listens on 127.0.0.1:3000 and answers for YOUR_DOMAIN (www redirects to it), and a dashboard on 127.0.0.1:8080 answers for app.YOUR_DOMAIN. Follow one half only: two web servers cannot share ports 80 and 443.

Caddy in one file

Debian's own caddy package is version 2.6.2 from 2022, and trixie-backports stops at 2.11.2, without the security fixes of 2.11.3, 2.11.4 and 2.11.6. Caddy's own repository carries the current release, and the same commands work on Ubuntu 24.04 and 26.04.

  1. Add Caddy's signing key and repository, then install it. The package runs Caddy as a service under its own caddy user, and the last two lines check it:

    # as root
    apt install -y debian-keyring debian-archive-keyring apt-transport-https curl gnupg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
    chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    chmod o+r /etc/apt/sources.list.d/caddy-stable.list
    apt update
    apt install -y caddy
    caddy version
    curl -I http://localhost

    caddy version should print v2.11.7 or newer with a build hash, and curl should get HTTP/1.1 200 OK with Server: Caddy. If apt update ever rejects the dl.cloudsmith.io key, rerun the second line and answer y. The key expired in late December 2025 and was re-signed. A rejection that survives that is on Cloudsmith's side: until it was fixed on 1 October 2026, the repository was signed with an expired subkey for weeks, which Debian 13 refused outright while Ubuntu only warned and kept the old Caddy.

  2. Run nano /etc/caddy/Caddyfile, delete what is there and paste this with your domain and email:

    # as root: the whole of /etc/caddy/Caddyfile
    {
    	email YOUR_EMAIL
    }
    
    YOUR_DOMAIN {
    	reverse_proxy 127.0.0.1:3000
    }
    
    www.YOUR_DOMAIN {
    	redir https://YOUR_DOMAIN{uri} permanent
    }
    
    app.YOUR_DOMAIN {
    	reverse_proxy 127.0.0.1:8080
    }

    No certificate paths, no port 80 block; permanent makes the redirect a 301. The Caddy automatic HTTPS docs list what it needs: DNS records pointing here, ports 80 and 443 open from outside and free for Caddy to bind, a writable, persistent data directory and the domain named in the config.

  3. Validate the file, reload without downtime and look for the new certificates:

    # as root
    caddy validate --config /etc/caddy/Caddyfile
    systemctl reload caddy
    journalctl -u caddy --no-pager | grep -i "certificate obtained"
    curl -I https://YOUR_DOMAIN
    curl -I https://www.YOUR_DOMAIN

    Good output is Valid configuration, a certificate obtained successfully line per hostname (if empty, wait a minute and repeat), HTTP/2 with an alt-svc line for h3, then HTTP/2 301 for www. Timeout during connect (likely firewall problem) in the journal means port 80 is blocked: back to step 2.

Never test with caddy run as root, because it clashes with the service and stores certificates in root's home.

Nginx and Certbot, spelled out

Debian 13 ships nginx 1.26.3 and Certbot 4.0, which renews with a third of the lifetime left through a twice-daily systemd timer.

  1. Install Nginx, Certbot and Certbot's Nginx plugin. Nginx starts on its own:

    # as root
    apt install -y nginx certbot python3-certbot-nginx curl
    nginx -v
    curl -I http://localhost

    The version line reads nginx version: nginx/1.26.3, and the default page answers HTTP/1.1 200 OK.

  2. WebSockets, long-lived two-way connections that start as an HTTP request with an Upgrade header, need headers Nginx does not forward by default. Save this map once as /etc/nginx/conf.d/websocket.conf:

    # as root: the whole of /etc/nginx/conf.d/websocket.conf
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
  3. Create /etc/nginx/sites-available/YOUR_DOMAIN with nano and paste:

    # as root: the whole of /etc/nginx/sites-available/YOUR_DOMAIN
    server {
        listen 80;
        listen [::]:80;
        server_name YOUR_DOMAIN www.YOUR_DOMAIN;
    
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    
        location / {
            proxy_pass http://127.0.0.1:3000;
        }
    }

    Later location blocks inherit these headers. The next block copies the file for the dashboard (new name, port 8080), enables both sites and tests the syntax:

    # as root
    sed -e 's/server_name .*;/server_name app.YOUR_DOMAIN;/' -e 's/127.0.0.1:3000/127.0.0.1:8080/' /etc/nginx/sites-available/YOUR_DOMAIN > /etc/nginx/sites-available/app.YOUR_DOMAIN
    ln -s /etc/nginx/sites-available/YOUR_DOMAIN /etc/nginx/sites-enabled/
    ln -s /etc/nginx/sites-available/app.YOUR_DOMAIN /etc/nginx/sites-enabled/
    nginx -t
    systemctl reload nginx

    nginx -t must report syntax is ok and test is successful, or it names the line to fix.

  4. Certbot finds each block by server_name, passes the port 80 check and adds HTTPS plus a redirect (--agree-tos accepts Let's Encrypt's subscriber agreement):

    # as root
    certbot --nginx --non-interactive --agree-tos -m YOUR_EMAIL --redirect -d YOUR_DOMAIN -d www.YOUR_DOMAIN
    certbot --nginx --non-interactive --agree-tos -m YOUR_EMAIL --redirect -d app.YOUR_DOMAIN

    Each run ends with Congratulations! You have successfully enabled HTTPS. If you see Could not automatically find a matching server block, a -d name is missing from every server_name.

  5. HTTP/2 and the www redirect are yours to add. Paste this under server_name in the YOUR_DOMAIN block that now has listen 443 ssl:

    # as root: add inside the server block that has "listen 443 ssl"
        http2 on;
        if ($host = www.YOUR_DOMAIN) {
            return 301 https://YOUR_DOMAIN$request_uri;
        }

    Add only http2 on; to the app file. Then test, reload and check:

    # as root
    nginx -t
    systemctl reload nginx
    curl -I https://www.YOUR_DOMAIN
    certbot renew --dry-run

    The www request should come back as HTTP/2 301, and the dry run should end with Congratulations, all simulated renewals succeeded.

What the two halves show

Caddy: 12 lines for three hostnames. Nginx: about 35, and roughly 70 once Certbot is done. The extra lines buy visibility, which is why admins who inherit a server often prefer Nginx, but writing them the first time takes an evening.

HTTP/3 widens the gap. In Cloudflare's Radar 2025 Year in Review, 21% of requests to Cloudflare used it. Caddy serves it by default; Debian's nginx needs manual quic listeners in a module still marked experimental, the source of six nginx CVEs fixed in 2024 and three more fixed between May and September 2026, according to nginx's security advisories. Take HTTP/3 free from Caddy or Traefik and skip it on Nginx.

Ubuntu 26.04 LTS, released on 23 April 2026, ships nginx 1.28.3 and Certbot 4.0, so the Nginx steps above work there too; only the version line differs. On Ubuntu 24.04, nginx 1.24 needs listen 443 ssl http2; instead of http2 on; and has no HTTP/3, and apt's Certbot 2.9.0 renews at a fixed 30 days, so use the Certbot snap.

Traefik, if everything already runs in Docker

Traefik also ships as a plain binary, but its quick start and this guide use the official Docker image, so install Docker from Docker's repository first, following Docker's Debian install page line by line (our guide to Docker on a VPS walks through it, including the ufw trap). Use the v3.7 tag, because 3.6 stopped getting security fixes on 16 August 2026, the old v2.11 branch on 7 September 2026, and Docker Engine 29 (November 2025) refuses v3 builds older than 3.6.1 with client version 1.24 is too old. Stop Caddy or Nginx first with systemctl disable --now caddy (or nginx), or Traefik cannot take ports 80 and 443.

  1. Make a folder for Traefik's certificates and open a new Compose file:

    # as root
    mkdir -p /opt/traefik/letsencrypt
    nano /opt/traefik/docker-compose.yml

    Paste this (whoami is a test app standing in for yours):

    # as root: the whole of /opt/traefik/docker-compose.yml
    services:
      traefik:
        image: traefik:v3.7
        restart: unless-stopped
        command:
          - --providers.docker=true
          - --providers.docker.exposedbydefault=false
          - --entrypoints.web.address=:80
          - --entrypoints.websecure.address=:443
          - --entrypoints.websecure.http3
          - --entrypoints.web.http.redirections.entrypoint.to=websecure
          - --entrypoints.web.http.redirections.entrypoint.scheme=https
          - --certificatesresolvers.le.acme.httpchallenge=true
          - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
          - --certificatesresolvers.le.acme.email=YOUR_EMAIL
          - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
        ports:
          - "80:80"
          - "443:443"
          - "443:443/udp"
        volumes:
          - ./letsencrypt:/letsencrypt
          - /var/run/docker.sock:/var/run/docker.sock:ro
    
      whoami:
        image: traefik/whoami
        labels:
          - traefik.enable=true
          - traefik.http.routers.whoami.rule=Host(`app.YOUR_DOMAIN`)
          - traefik.http.routers.whoami.entrypoints=websecure
          - traefik.http.routers.whoami.tls.certresolver=le
  2. Start both containers in the background, then test the route:

    # as root
    cd /opt/traefik
    docker compose up -d
    curl -I https://app.YOUR_DOMAIN

    The answer should be HTTP/2 200. A certificate error in the first minute is normal, so retry; if the browser keeps showing TRAEFIK DEFAULT CERT, ACME failed and docker compose logs traefik says why.

Each new app gets the same four labels and no ports: section, because Docker-published ports bypass ufw. Traefik's docs warn that if Traefik is attacked, the attacker may reach the host through the Docker API. The :ro flag does not change that, since it stops writes to the socket file and not API calls through it. Leave out the quick start's unauthenticated --api.insecure=true dashboard.

Hardening the proxy once HTTPS works

Security headers and HSTS

None of the three sends HSTS by default. HSTS (Strict-Transport-Security) tells browsers to use only HTTPS for your host for max-age seconds. In Caddy, define this snippet below the global options and add import secure inside each site block:

# as root: add to /etc/caddy/Caddyfile below the global options block
(secure) {
	header {
		Strict-Transport-Security "max-age=31536000"
		X-Content-Type-Options nosniff
		X-Frame-Options DENY
		Referrer-Policy strict-origin-when-cross-origin
		-Server
	}
}

In Nginx, the same headers go in each listen 443 ssl block:

# as root: add inside each server block that has "listen 443 ssl"
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;

Without always, Nginx skips them on error pages, and one add_header inside a location silently drops all server-level ones there. nginx 1.30, the stable branch since April 2026, can merge the two levels instead with add_header_inherit merge;, but Debian 13's 1.26.3 and Ubuntu 26.04's 1.28.3 reject that line as an unknown directive, so repeat the headers in such a location. Add includeSubDomains and preload only when every subdomain serves HTTPS; leaving the preload list takes months.

Basic auth for admin panels

Every hostname you certify lands in public Certificate Transparency logs, so admin.YOUR_DOMAIN is public the day it gets HTTPS. Put basic auth, the browser's own login prompt, in front, or make the panel reachable only through your own WireGuard VPN on Debian 13. Caddy makes the hash itself; type the password twice and it prints a bcrypt hash:

# as root
caddy hash-password

Paste the hash into this block, then validate and reload as in step 6 (Caddy 2.8 renamed the old basicauth directive):

# as root: add to /etc/caddy/Caddyfile
admin.YOUR_DOMAIN {
	basic_auth {
		admin PASTE_THE_HASH_HERE
	}
	reverse_proxy 127.0.0.1:9000
}

Nginx needs htpasswd from apache2-utils, and it asks for the password twice:

# as root
apt install -y apache2-utils
htpasswd -c /etc/nginx/.htpasswd admin

Make the file readable by Nginx's workers but not by other users:

# as root
chown root:www-data /etc/nginx/.htpasswd
chmod 640 /etc/nginx/.htpasswd

Then add auth_basic "Restricted"; and auth_basic_user_file /etc/nginx/.htpasswd; to the location and reload. Anonymous requests now get 401.

Rate limiting login pages

Imperva's 2026 Bad Bot Report put automated traffic at more than 53% of all web traffic in 2025, up from 51% a year earlier. Rate limiting, a cap on requests per client per second, makes password guessing slow. For Nginx, one small file defines a zone that allows one request per second per IP:

# as root: the whole of /etc/nginx/conf.d/ratelimit.conf
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_req_status 429;

Add limit_req zone=login burst=5 nodelay; inside a location for your login path: five quick retries pass, the rest get 429 instead of the default 503. Traefik has a RateLimit middleware; Caddy needs the third-party caddy-ratelimit module in a custom build.

Real visitor IPs behind Cloudflare

Behind Cloudflare's proxy, requests come from Cloudflare addresses and the visitor's IP sits in CF-Connecting-IP. Restore it before any IP-based limit, or one bucket throttles everyone. Trust it only from Cloudflare's ranges, which its guide to restoring visitor IPs says change over time. For Nginx, this builds the list and reloads:

# as root
{ for ip in $(curl -s https://www.cloudflare.com/ips-v4) $(curl -s https://www.cloudflare.com/ips-v6); do echo "set_real_ip_from $ip;"; done; echo "real_ip_header CF-Connecting-IP;"; } > /etc/nginx/conf.d/cloudflare-realip.conf
nginx -t
systemctl reload nginx

After a few visits, the access log shows visitor addresses. Caddy uses trusted_proxies and client_ip_headers in its global servers options, Traefik forwardedHeaders.trustedIPs. Set Cloudflare's SSL/TLS mode to Full (strict) as well. In Flexible mode Cloudflare talks plain HTTP to your server, your proxy redirects it, and the browser ends at ERR_TOO_MANY_REDIRECTS.

Renewal checks, updates and backups

Automatic renewal still needs watching. In Keyfactor's 2025 survey of 450 PKI practitioners, 86% had an outage from an expired or mismanaged certificate within a year. On the Nginx route the first two lines show Certbot's timer and certificates; on any route the last one shows what the live site serves:

# as root
systemctl list-timers | grep certbot
certbot certificates
echo | openssl s_client -connect YOUR_DOMAIN:443 -servername YOUR_DOMAIN 2>/dev/null | openssl x509 -noout -issuer -enddate

The output lists the next certbot.timer run, the expiry dates, and an issuer with Let's Encrypt (or ZeroSSL, Caddy's fallback) above a notAfter date. Then let an uptime monitor with a certificate check alert you below 14 days; our guide to Uptime Kuma monitoring from a second location sets one up.

Caddy and Nginx update with apt update && apt upgrade. Run caddy version afterwards, because apt keeps the old build when a repository fails its signature check. Debian's nginx stays at 1.26.3 on paper while its security team backports fixes, such as the May 2026 rewrite-module heap overflow CVE-2026-42945, so a skipped upgrade leaves holes the version number does not show. Traefik updates with docker compose pull and docker compose up -d, the tag pinned to v3.7. Each Traefik minor release gets six months of support from its release date, and 3.7 came out on 5 May 2026, so when the Traefik releases page lists a newer minor, move the tag to it and pull again.

The free weekly backup covers the whole server, but between runs, archive config and certificates nightly and copy them off the server (our guide to off-site backups with restic automates that part), so a rebuild reuses them instead of using up the five-per-week limit for the same names. Run only your proxy's line:

What do the common proxy and certificate errors mean?

502 Bad Gateway

The app is not answering where the proxy looks. Nginx logs connect() failed (111: Connection refused) while connecting to upstream. Check ss -tlnp. Usual culprits: localhost resolving to ::1, a container app listening on 127.0.0.1 inside its container, or an app on another Docker network than Traefik's.

DNS problem: NXDOMAIN looking up A

The name has no A record, often the www one nobody created. Add it, wait until dig +short returns your IP, then retry; five failed validations for one hostname within an hour block further attempts, and one attempt comes back every 12 minutes.

Other errors

Frequently asked questions

Can I host multiple HTTPS websites on one IP address?

Yes. Browsers name the host they want inside the TLS handshake (SNI, defined in RFC 6066), so one reverse proxy on one IPv4 address picks the right certificate per site. All three proxies do this out of the box.

Does Caddy renew Let's Encrypt certificates automatically?

Yes. It renews when about a third of the lifetime is left, or earlier when Let's Encrypt's renewal information (ARI) says so, backs off between retries and falls back to ZeroSSL if Let's Encrypt keeps failing. It needs ports 80 and 443 reachable and a persistent data directory.

Do I need port 80 open for Let's Encrypt?

For HTTP-01, yes, open to everyone, since validation comes from unpublished addresses. Let's Encrypt's own advice is that an open port 80 adds no larger attack surface, because the same software answers on port 443. TLS-ALPN-01 on port 443 and DNS-01 are the alternatives.

How do I get a wildcard certificate from Let's Encrypt?

Only through DNS-01, with an API token for your DNS provider on the server. Traefik includes the providers, Certbot uses a DNS plugin and Caddy needs a custom build. Limit the token to DNS edits on one zone. DNS-PERSIST-01, a TXT record you set once with no token on the server, was announced in February 2026 for the second quarter, but Let's Encrypt has not switched it on in production yet.

Can I get a Let's Encrypt certificate for an IP address without a domain?

Yes, since 15 January 2026, but IP address certificates come only with the six-day shortlived profile, so renewal runs every few days. Certbot 5.4 or newer (the snap, not Debian 13's 4.0) fetches one with --ip-address, --preferred-profile shortlived and the webroot plugin, but cannot install it into Nginx, and Caddy by default still serves IP addresses with its own self-signed certificate. A domain name remains the easier route.

Can RS Computers set up the proxy and certificates for me?

Yes. Message us on Telegram or email info@rscomputers-ks.com with your domains and the apps you want behind the proxy, and we will suggest a plan and quote the setup.

A sensible order for a new server

For the two apps in this guide, VPS Micro with Debian 13 is a comfortable start, with room for Caddy and a third light app later. Order it from the VPS and VDS plans page and create the DNS records as soon as the IP addresses arrive, since they take a while to spread. Then work through the three preparation steps and the 12-line Caddyfile. Before you hand out the addresses, add the headers snippet and an expiry monitor. Anything called admin gets a password.

← All articles

Chat on Telegram