OPNsenseLab
HAProxy on an OPNsense gateway routing HTTPS to backend servers with a Let's Encrypt certificate
Networking

HAProxy Reverse Proxy on OPNsense with Let's Encrypt

Use the os-haproxy and os-acme-client plugins to put one HTTPS front end in front of all your homelab services on OPNsense — real backends, SNI host

By OPNsenseLab Editorial · · 7 min read

A reverse proxy lets you reach many internal services — Jellyfin, a Git server, a dashboard — through one HTTPS endpoint, with the firewall terminating TLS and routing by hostname. OPNsense can do this natively with the os-haproxy plugin, and os-acme-client handles Let’s Encrypt certificates automatically. This guide builds a working host-based router with valid certs.

Before you touch the config, understand the model. HAProxy has frontends (where clients connect — typically :443), backends (your real internal servers), and conditions/rules that map an incoming hostname to a backend. The plugin’s terminology splits these into Real Servers, Backend Pools, Rules, Conditions, and Public Services — it’s more granular than a typical proxy config, and that’s the main thing that trips people up. Build the pieces bottom-up: real server → backend pool → condition → rule → public service (frontend).

One prerequisite decides whether this is even worth it: a resolvable hostname. Let’s Encrypt issues certificates for names, not IPs. You need either a real domain you control or a Dynamic DNS hostname, plus a way to pass the ACME challenge (covered below). If you only ever reach services by IP on the LAN, a reverse proxy with public certs is overkill — a self-signed cert and a bookmark are simpler.

Step 1: Free up port 443 on the firewall

HAProxy’s HTTPS frontend wants TCP 443, but the OPNsense web GUI defaults to it. In System → Settings → Administration:

  • Change the TCP port for the web GUI to something else (e.g., 4443).
  • Disable the HTTP redirect so the GUI isn’t competing for 80 either.
  • Keep Listen interfaces on LAN only — exposing the management GUI is never the goal here.

Re-log in to the GUI on the new port to confirm before continuing.

Step 2: Install the plugins

System → Firmware → Plugins → install os-haproxy and os-acme-client.

After install: HAProxy lives under Services → HAProxy, and the ACME client under Services → ACME Client.

Step 3: Get a Let’s Encrypt certificate with the ACME client

Services → ACME Client:

  1. Accounts → Add: enter your email, choose Let’s Encrypt as the CA (use the staging CA first if you want to test without hitting rate limits).
  2. Challenge Types → Add: the simplest robust option for a homelab is DNS-01 if your DNS provider is supported (Cloudflare, deSEC, etc.) because it works even when 80/443 aren’t publicly reachable and supports wildcard certs. HTTP-01 also works but requires inbound port 80 reaching the firewall, which you then have to hand to HAProxy.
  3. Automations → Add: create an automation that restarts HAProxy after a certificate is issued or renewed, so the new cert is loaded without manual intervention.
  4. Certificates → Add: enter your hostname(s) (e.g., jellyfin.example.com, or a wildcard *.example.com with DNS-01), and attach the account, challenge, and automation. Issue it and confirm it shows a valid not-after date.

Using DNS-01 with a wildcard means you issue one cert and add new subdomains later without re-validating each — the cleanest pattern for a growing homelab.

Step 4: Define the backend (your real service)

Services → HAProxy → Real Servers → Add:

  • Name: jellyfin-server
  • FQDN or IP: the internal IP of the service (e.g., 192.168.10.50)
  • Port: the service’s real port (e.g., 8096)

Then Backend Pools → Add:

  • Name: jellyfin-pool
  • Servers: select jellyfin-server
  • Leave health checking on its sensible default; HAProxy will mark a dead backend down rather than sending traffic into a black hole.

Step 5: Map hostname → backend with a condition and rule

Rules & Checks → Conditions → Add:

  • Type: Host matches (HTTP Host header)
  • Host: jellyfin.example.com

Rules & Checks → Rules → Add:

  • Condition: the host condition above
  • Action: use the specified backend pool → jellyfin-pool

Repeat steps 4–5 per service. Each service is a real server + pool + host condition + rule; the frontend stays shared.

Step 6: Create the public service (frontend)

Virtual Services → Public Services → Add:

  • Listen Address: 0.0.0.0:443 (or your WAN/LAN address as appropriate)
  • Type: HTTP / HTTPS (SSL offloading)
  • Certificates: select the Let’s Encrypt cert from Step 3
  • Select Rules: attach the host rules you created

Optionally add a second frontend on :80 whose only job is to 301-redirect to HTTPS.

Step 7: Enable and apply

Services → HAProxy → Settings → check Enable HAProxy, Save, then Apply. Add a WAN (or LAN) firewall rule permitting TCP 443 to the HAProxy listen address if you’re fronting anything reachable from outside the segment.

Verify it works

  1. Service is up: Services → HAProxy → the Stats page (enable the stats listener if you want it) shows each backend pool green/UP.
  2. Certificate is valid: browse to https://jellyfin.example.com and confirm the padlock shows a Let’s Encrypt cert with the right name — no warning.
  3. Routing is correct: a second hostname (git.example.com) lands on its own backend, not the first one. If everything resolves to one service, your host conditions are wrong or a catch-all rule is ordered above the specific ones.

When HAProxy on OPNsense is the wrong tool

Running the proxy on the firewall couples your service reachability to the firewall’s load — fine for a homelab, less ideal if you’d rather keep the firewall lean, in which case a dedicated Nginx/Caddy/Traefik box is cleaner. If you need a polished UI for managing many vhosts and certs, Nginx Proxy Manager on a small VM is friendlier than HAProxy’s granular plugin. And never expose an internal admin panel through the proxy without authentication in front of it — a reverse proxy publishes services; it doesn’t secure them. For services you genuinely want remote, consider putting them behind the WireGuard road-warrior VPN instead of the public internet.

Comparing approaches? FirewallCompare covers reverse-proxy options on OPNsense vs pfSense. The OPNsense reverse proxy documentation is the authoritative reference for the plugin’s fields.

Related

Comments