Let's Encrypt and ACME on OPNsense: Free TLS Certificates for All Your Internal Services

Deploy Let's Encrypt on OPNsense with the os-acme-client plugin: DNS-01 Cloudflare challenge, wildcard *.botum.ca certificate, HAProxy/Nginx/Docker distribution, automatic renewal and alerting.

Let's Encrypt and ACME on OPNsense: Free TLS Certificates for All Your Internal Services
Why TLS for internal services - HSTS and browser security
Internal TLS: HSTS, browser trust, Zero-Trust — essential even on the LAN

1. Why TLS Even for Internal Services?

Most administrators skip TLS on the internal network: "nobody can intercept LAN traffic anyway". This is a critical mistake. Here's why:

  • HSTS (HTTP Strict Transport Security): modern browsers refuse HTTP connections to domains that previously responded over HTTPS. Without internal TLS, your services become unreachable after a first HTTPS visit.
  • Browser trust: "invalid certificate" security warnings train users to ignore alerts — making them vulnerable to real attacks.
  • Internal Zero-Trust: an attacker on the IoT VLAN (Episode 2) can intercept unencrypted traffic. Internal TLS = defense in depth.
  • APIs and webhooks: Grafana, Wazuh, AdGuard Home expose APIs. Without TLS, authentication tokens travel in plaintext.
  • Let's Encrypt = free: the OPNsense ACME plugin automates everything — no economic reason to skip it.

2. Installing the os-acme-client Plugin on OPNsense

The os-acme-client plugin is available directly in the OPNsense plugin manager. It integrates the acme.sh ACME client and natively manages Let's Encrypt.

# Via OPNsense Web UI:
# System > Firmware > Plugins > search "acme"
# Install: os-acme-client
# Restart OPNsense after installation

# Menu available under:
# Services > ACME Client > Settings / Accounts / Certificates / Automations

# Create ACME account:
# Services > ACME Client > Accounts > Add
Name        : botum-letsencrypt
E-Mail      : admin@botum.ca
ACME Server : Let's Encrypt (production)
              https://acme-v02.api.letsencrypt.org/directory
DNS-01 ACME challenge flow - validation without port 80
DNS-01 Challenge: Let's Encrypt validation via Cloudflare TXT record — no port 80 required

3. Domain Validation: DNS Challenge Without Port 80

The DNS-01 validation is the only method that works without exposing port 80 publicly — essential for internal services and wildcard certificates.

# Cloudflare — minimal API token (recommended):
# Cloudflare Dashboard > My Profile > API Tokens > Create Token
# Permissions: Zone > DNS > Edit | Zone: Specific zone > botum.ca

# OPNsense > Services > ACME Client > Challenge Types > Add
Name          : cloudflare-dns
Challenge     : DNS-01
DNS Service   : Cloudflare
CF_Token      : [your-cloudflare-api-token]
CF_Account_ID : [your-account-id]

# OVH: OVH_AK / OVH_AS / OVH_CK / OVH_ENDPOINT : ovh-ca
# Gandi LiveDNS: GANDI_LIVEDNS_KEY : [Gandi API key]

4. Generating a Wildcard Certificate *.botum.ca

A wildcard certificate covers all subdomains: grafana.botum.ca, wazuh.botum.ca, adguard.botum.ca. One certificate, centralized renewal.

# Services > ACME Client > Certificates > Add
Name               : wildcard-botum-ca
Common Name        : *.botum.ca
Challenge Type     : cloudflare-dns
Key Length         : 4096 bit (or ec-384 for ECDSA)
Auto Renew         : enable
Renew before (d)   : 30

# Click "Issue / Renew" — the ACME plugin will:
# 1. Contact Let's Encrypt
# 2. Create DNS TXT record _acme-challenge.botum.ca via Cloudflare API
# 3. Let's Encrypt validates the TXT record
# 4. Certificate downloaded and stored in OPNsense
# 5. TXT record automatically deleted

# Verify: System > Trust > Certificates
# -> wildcard-botum-ca with ~90 days expiry
Wildcard certificate distribution to OPNsense HAProxy Nginx Docker
Wildcard certificate distribution: OPNsense, HAProxy, Nginx, Docker services — one cert, all services secured

5. Distributing the Certificate: OPNsense, HAProxy, Nginx, Docker

5.1 OPNsense Web UI

# System > Settings > Administration
SSL Certificate : wildcard-botum-ca
# -> Admin UI switches to valid HTTPS, accessible via https://fw.botum.ca/

5.2 HAProxy (reverse proxy)

# Services > HAProxy > Settings > HTTPS Frontend
SSL Certificates : wildcard-botum-ca
SSL offloading   : enable
# HAProxy handles TLS termination for all backend services

5.3 Nginx + Docker

# Post-renewal automation in OPNsense:
# Services > ACME Client > Automations > Add
Name    : deploy-to-nginx
Command : /usr/local/sbin/deploy_cert_nginx.sh

# Docker Compose — mount certificate as volume:
services:
  grafana:
    volumes:
      - /opt/certs/botum.ca.crt:/etc/grafana/ssl/cert.pem:ro
      - /opt/certs/botum.ca.key:/etc/grafana/ssl/key.pem:ro
    environment:
      GF_SERVER_PROTOCOL: https
      GF_SERVER_CERT_FILE: /etc/grafana/ssl/cert.pem
      GF_SERVER_CERT_KEY: /etc/grafana/ssl/key.pem
Certificate auto-renewal monitoring ACME Grafana alerting
Automatic renewal + alerting: native ACME cron, Prometheus ssl_exporter, Telegram notifications on expiry

6. Automatic Renewal (Native ACME Cron)

The plugin includes a native cron that automatically renews certificates 30 days before expiry. Let's Encrypt issues certificates valid for 90 days.

# Services > ACME Client > Settings
# [x] Enable Cron Job
# Run interval : Daily
# Scheduled at : 03:30

# Check ACME logs:
tail -100 /var/log/acme.log | grep -E "SUCCESS|ERROR|Renew"

7. Expiry Alerting (Monitoring Integration from Episode 9)

#!/bin/bash
# /opt/scripts/check_cert_expiry.sh
TELEGRAM_TOKEN="[token]"
TELEGRAM_CHAT="-1001234567890"
WARN_DAYS=14 ; CRIT_DAYS=7

for cert in grafana.botum.ca wazuh.botum.ca adguard.botum.ca fw.botum.ca; do
    EXPIRY=$(echo | openssl s_client -connect ${cert}:443 -servername ${cert} 2>/dev/null              | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
    DAYS_LEFT=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
    [ $DAYS_LEFT -le $CRIT_DAYS ] &&         curl -s "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage"              -d "chat_id=${TELEGRAM_CHAT}&text=CRITICAL: Cert ${cert} expires in ${DAYS_LEFT} days!" > /dev/null
done

# Prometheus ssl_exporter alert rule:
# expr: ssl_cert_not_after - time() < 86400 * 14
BOTUM stack secured with TLS - AdGuard DoH Grafana Wazuh OPNsense
Full BOTUM stack secured with TLS: AdGuard Home DoH, Grafana, Wazuh Dashboard and OPNsense with valid certificates

8. Use Cases: AdGuard DoH, Grafana, Wazuh Dashboard

8.1 AdGuard Home DNS-over-HTTPS (Episode 8)

# AdGuard Home > Settings > Encryption Settings
Enable encryption : Yes
Server name       : adguard.botum.ca
DNS-over-HTTPS    : https://adguard.botum.ca/dns-query
TLS certificate   : /etc/adguard/ssl/botum.ca.crt
Private key       : /etc/adguard/ssl/botum.ca.key

8.2 Grafana (Episode 9)

# grafana.ini
[server]
protocol       = https
cert_file      = /etc/grafana/ssl/cert.pem
cert_key       = /etc/grafana/ssl/key.pem
domain         = grafana.botum.ca
root_url       = https://grafana.botum.ca/
enforce_domain = true

8.3 Wazuh Dashboard (Episode 11)

# /etc/wazuh-dashboard/opensearch_dashboards.yml
server.ssl.enabled: true
server.ssl.certificate: /etc/wazuh-dashboard/certs/wazuh-dashboard.pem
server.ssl.key: /etc/wazuh-dashboard/certs/wazuh-dashboard-key.pem
# Replace self-signed certs with botum.ca.crt / botum.ca.key
# systemctl restart wazuh-dashboard

9. Conclusion

With Episode 14, all services in the BOTUM OPNsense Stack are secured with a valid TLS certificate, automatically renewed, with zero manual intervention:

  • os-acme-client — Native OPNsense plugin, Let's Encrypt integration
  • DNS-01 challenge — Validation without port 80, Cloudflare/OVH/Gandi support
  • Wildcard *.botum.ca — One certificate for all subdomains
  • HAProxy + Nginx + Docker — Automated post-renewal distribution
  • Native ACME cron — Automatic renewal 30 days before expiry
  • Telegram + Prometheus alerting — Proactive expiry monitoring

Coming next → Episode 15 (final): Netflow + ntopng on OPNsense — real-time network traffic analysis, the last episode of the series!

📥 Guide PDF complet

Téléchargez ce guide en PDF pour le consulter hors ligne.

⬇ Télécharger le guide (PDF)

🚀 Aller plus loin avec BOTUM

Ce guide couvre les bases. En production, chaque environnement a ses spécificités. Les équipes BOTUM accompagnent les organisations dans le déploiement, la configuration avancée et la sécurisation de leur infrastructure. Si vous avez un projet, parlons-en.

Discuter de votre projet →
OPNsense Series 📋 Complete series →