Auto-update blog content from Obsidian: 2026-04-29 19:59:52
Some checks failed
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Successful in 31s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Test-Staging (push) Failing after 3s
Blog Deployment / Merge (push) Has been skipped
Blog Deployment / Deploy-Production (push) Has been skipped
Blog Deployment / Test-Production (push) Has been skipped
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s

This commit is contained in:
Gitea Actions
2026-04-29 19:59:52 +00:00
parent c731b7eaf5
commit 5936475f99
39 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,588 @@
---
slug: opnsense-ha-full-configuration
title: OPNsense HA dans Proxmox, ma Configuration Homelab Complète
description: "Configuration complète d'un cluster OPNsense HA dans Proxmox : interfaces et VIP, parefeu, DHCP Dnsmasq, DNS Unbound, VPN WireGuard et reverse proxy Caddy."
date: 2025-10-23
draft: false
tags:
- opnsense
- high-availability
- proxmox
- unbound-dns
- caddy
- wireguard
- dnsmasq
categories:
- homelab
---
## Intro
Dans mon précédent [article]({{< ref "post/12-opnsense-virtualization-highly-available" >}}) j'ai mis en place un PoC pour valider la construction d'un cluster de deux VM **OPNsense** dans **Proxmox VE** afin de rendre le parefeu hautement disponible.
Maintenant je prépare la mise en œuvre dans mon homelab, cet article documente ma configuration réelle du cluster OPNsense, depuis de nouvelles installations jusqu'à la HA, le DNS, le DHCP, le VPN et le reverse proxy.
### Contexte
Avant d'entrer dans la configuration d'OPNsense, un peu de contexte pour comprendre les choix que j'ai faits.
Dans mon cluster Proxmox VE, j'ai créé 2 VM et installé OPNsense. L'objectif est de remplacer ma unique machine physique par ce cluster. Chaque VM possède 7 NICs pour les réseaux suivants :
- **vmbr0** : _Mgmt_
- **vlan20** : _WAN_
- **vlan13** : _User_
- **vlan37** : _IoT_
- **vlan44** : _pfSync_
- **vlan55** : _DMZ_
- **vlan66** : _Lab_
Initialement je pensais simplement restaurer ma configuration actuelle sur la VM fraîchement installée. Mais j'ai réalisé que je n'avais pas vraiment documenté comment j'avais assemblé les éléments la première fois. C'est le moment parfait pour remettre les choses en ordre.
⚠️ Je ne peux disposer que d'une seule IP WAN, partagée entre les nœuds, fournie par le DHCP de ma box opérateur. Pour cette raison je n'aurai pas de VIP pour le WAN et je dois trouver une solution pour partager cette unique IP.
J'espère que, dans le prochain article, si ce projet arrive sur mon réseau de production, je couvrirais aussi la création des VM dans Proxmox et la façon dont je prépare la migration de ma box OPNsense physique vers ce cluster HA en VM. Allonsy !
---
## Système
### Général
Je commence par les bases, dans `System` > `Settings` > `General` :
- **Hostname** : `cerbere-head1` (`cerbere-head2` pour la seconde).
- **Domain** : `mgmt.vezpi.com`.
- **Time zone** : `Europe/Paris`.
- **Language** : `English`.
- **Theme** : `opnsense-dark`.
- **Prefer IPv4 over IPv6** : cocher la case pour préférer IPv4.
### Utilisateurs
Ensuite, dans `System` > `Access` > `Users`, je crée un nouvel utilisateur plutôt que d'utiliser `root`, l'ajoute au groupe `admins`, et retire `root` de ce groupe.
### Administration
Dans `System` > `Settings` > `Administration`, je modifie plusieurs éléments :
- **Web GUI**
- **TCP port** : de `443` à `4443`, pour libérer le port 443 pour le reverse proxy à venir.
- **HTTP Redirect** : Désactivé, pour libérer le port 80 pour le reverse proxy.
- **Alternate Hostnames** : `cerbere.vezpi.com` qui sera l'URL pour atteindre le parefeu via le reverse proxy.
- **Access log** : Activé.
- **Secure Shell**
- **Secure Shell Server** : Activé.
- **Root Login** : Désactivé.
- **Authentication Method :** Autoriser la connexion par mot de passe (pas de login `root`).
- **Listen Interfaces** : _Mgmt_
- **Authentication**
- **Sudo** : `No password`.
Une fois que je clique sur `Save`, je suis le lien fourni pour atteindre la WebGUI sur le port `4443`.
### Mises à Jour
Il est temps de mettre à jour, dans `System` > `Firmware` > `Status`, je vérifie les mises à jour du firmware et les applique (nécessite un redémarrage).
### QEMU Guest Agent
Une fois mis à jour et redémarré, je vais dans `System` > `Firmware` > `Plugins`, je coche l'option pour afficher les plugins communautaires. J'installe que le **QEMU Guest Agent**, `os-qemu-guest-agent`, pour permettre la communication entre la VM et l'hôte Proxmox.
Cela nécessite un arrêt. Dans Proxmox, j'active le `QEMU Guest Agent` dans les options de la VM :
![Options d'une VM Proxmox avec QEMU Guest Agent activé](img/proxmox-opnsense-enable-qemu-guest-agent.png)
Finalement je redémarre la VM. Une fois démarrée, depuis la WebGUI de Proxmox, je peux voir les IPs de la VM ce qui confirme que le guest agent fonctionne.
---
## Interfaces
Sur les deux parefeu, j'assigne les NIC restantes à de nouvelles interfaces en ajoutant une description. Les VMs ont 7 interfaces, je compare attentivement les adresses MAC pour éviter de mélanger les interfaces :
![Assign interfaces menu in OPNsense](img/opnsense-assign-interfaces.png)
Au final, la configuration des interfaces ressemble à ceci :
| Interface | Mode | `cerbere-head1` | `cerbere-head2` |
| --------- | ----------- | --------------- | --------------- |
| *Mgmt* | Static IPv4 | 192.168.88.2/24 | 192.168.88.3/24 |
| *WAN* | DHCPv4/6 | Enabled | Disabled |
| *User* | Static IPv4 | 192.168.13.2/24 | 192.168.13.3/24 |
| *IoT* | Static IPv4 | 192.168.37.2/24 | 192.168.37.3/24 |
| *pfSync* | Static IPv4 | 192.168.44.1/30 | 192.168.44.2/30 |
| *DMZ* | Static IPv4 | 192.168.55.2/24 | 192.168.55.3/24 |
| *Lab* | Static IPv4 | 192.168.66.2/24 | 192.168.66.3/24 |
Je ne configure pas encore les Virtual IPs, je m'en occuperai une fois la haute disponibilité mise en place.
---
## Haute Disponibilité
### Règle Pare-feu pour pfSync
À partir d'ici nous pouvons associer les deux instances pour créer un cluster. La dernière chose que je dois faire est d'autoriser la communication sur l'interface _pfSync_. Par défaut, aucune communication n'est autorisée sur les nouvelles interfaces.
Dans `Firewall` > `Rules` > `pfSync`, je crée une nouvelle règle sur chaque parefeu :
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | *pfSync* |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | any |
| **Source** | *pfSync* net |
| **Destination** | *pfSync* net |
| **Destination port range** | from: any - to: any |
| **Log** | Log packets |
| **Category** | OPNsense |
| **Description** | pfSync |
### Configurer la HA
OPNsense HA utilise pfSync pour la synchronisation des états du parefeu (en temps réel) et XMLRPC Sync pour pousser la configuration et les services du master → backup (sens unique).
La HA est configurée dans `System` > `High Availability` > `Settings`
#### Master
- **General Settings**
- **Synchronize all states via**: _pfSync_
- **Synchronize Peer IP**: `192.168.44.2`, l'IP du nœud backup
- **Configuration Synchronization Settings (XMLRPC Sync)**
- **Synchronize Config**: `192.168.44.2`
- **Remote System Username**: `<username>`
- **Remote System Password**: `<password>`
- **Services to synchronize (XMLRPC Sync)**
- **Services**: Select All
#### Backup
- **General Settings**
- **Synchronize all states via**: _pfSync_
- **Synchronize Peer IP**: `192.168.44.1`, l'IP du nœud master
⚠️ Ne remplissez pas les champs XMLRPC Sync sur le nœud backup, ils doivent uniquement être remplis sur le master.
### Statut de la HA
Dans `System` > `High Availability` > `Status`, je peux vérifier si la synchronisation fonctionne. Sur cette page je peux répliquer un ou tous les services du master vers le nœud backup :
![OPNsense high availability status page](img/opnsense-high-availability-status.png)
---
## IPs Virtuelles
Maintenant que la HA est configurée, je peux attribuer à mes réseaux une IP virtuelle partagée entre mes nœuds. Dans `Interfaces` > `Virtual IPs` > `Settings`, je crée un VIP pour chacun de mes réseaux en utilisant **CARP** (Common Address Redundancy Protocol). L'objectif est de réutiliser les adresses IP utilisées par mon instance OPNsense actuelle, mais comme elle route encore mon réseau, j'utilise des IP différentes pour la phase de configuration :
![Liste des IPs virtuelles dans OPNsense](img/opnsense-interface-virtual-ips.png)
OPNsense permet CARP par défaut, aucune règle de parefeu spéciale requise
---
## Script de Bascule CARP
Dans ma configuration, je n'ai qu'une seule adresse WAN fournie par le DHCP de ma box opérateur. OPNsense ne propose pas nativement de moyen de gérer ce scénario. Pour s'en occuper, j'implémente la même astuce que j'ai utilisée dans le [PoC]({{< ref "post/12-opnsense-virtualization-highly-available" >}}).
### Copier l'Adresse MAC
Je copie la MAC de l'interface `net1` de `cerbere-head1` et la colle sur la même interface de `cerbere-head2`. Ainsi, le bail DHCP pour l'adresse WAN peut être partagé entre les nœuds.
⚠️ Attention : Avoir deux machines sur le réseau avec la même MAC peut provoquer des conflits ARP et casser la connectivité. Une seule VM doit garder son interface active.
### Script d'Evènement CARP
Sous le capot, dans OPNsense, un événement CARP déclenche certains scripts (lorsque le master meurt). Ils sont situés dans `/usr/local/etc/rc.syshook.d/carp/`.
Pour gérer l'interface WAN sur chaque nœud, j'implémente ce script PHP `10-wan` sur les deux nœuds, via SSH (n'oubliez pas de le rendre exécutable). Selon leur rôle (master ou backup), il activera ou désactivera leur interface WAN :
```php
#!/usr/local/bin/php
<?php
require_once("config.inc");
require_once("interfaces.inc");
require_once("util.inc");
require_once("system.inc");
$subsystem = !empty($argv[1]) ? $argv[1] : '';
$type = !empty($argv[2]) ? $argv[2] : '';
if ($type != 'MASTER' && $type != 'BACKUP') {
log_error("Carp '$type' event unknown from source '{$subsystem}'");
exit(1);
}
if (!strstr($subsystem, '@')) {
log_error("Carp '$type' event triggered from wrong source '{$subsystem}'");
exit(1);
}
$ifkey = 'wan';
if ($type === "MASTER") {
log_error("enable interface '$ifkey' due CARP event '$type'");
$config['interfaces'][$ifkey]['enable'] = '1';
write_config("enable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
} else {
log_error("disable interface '$ifkey' due CARP event '$type'");
unset($config['interfaces'][$ifkey]['enable']);
write_config("disable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
}
```
Dans `Interfaces` > `Virtual IPs` > `Status`, je peux forcer un événement CARP en entrant en `Persistent maintenance mode`. Le déclenchement permet de tester ce script, qui désactive l'interface WAN sur le master tout en l'activant sur le backup.
---
## Pare-feu
Configurons la fonctionnalité principale d'OPNsense, le parefeu. Je ne veux pas multiplier les règles inutilement. Je n'ai besoin de configurer que le master, grâce à la réplication.
### Groupes d'Interface
Globalement j'ai 2 types de réseaux : ceux en qui j'ai confiance et ceux en qui je n'ai pas confiance. Dans cette optique, je vais créer deux zones.
En règle générale, mes réseaux non fiables n'ont accès qu'au DNS et à Internet. Les réseaux fiables peuvent atteindre les autres VLANs.
Pour commencer, dans `Firewall` > `Groups`, je crée 2 zones pour regrouper mes interfaces :
- **Trusted** : _Mgmt_, _User_
- **Untrusted** : _IoT_, _DMZ_, _Lab_
### Network Aliases
Ensuite, dans `Firewall` > `Aliases`, je crée un alias `InternalNetworks` pour regrouper tous mes réseaux internes :
![Création d'alias pour les réseaux locaux dansOPNsense](img/opnsense-create-alias-internalnetworks.png)
### Règles de Pare-feu Rules
Pour tous mes réseaux, je veux autoriser les requêtes DNS vers le DNS local. Dans `Firewall` > `Rules` > `Floating`, créons la première règle
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Trusted, Untrusted |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | TCP/UDP |
| **Source** | InternalNetworks |
| **Destination** | This Firewall |
| **Destination port range** | from: DNS - to: DNS |
| **Log** | Log packets |
| **Category** | DNS |
| **Description** | DNS query |
Ensuite je veux autoriser les connexions vers Internet. Au même endroit je crée une seconde règle :
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Trusted, Untrusted |
| **Direction** | in |
| **TCP/IP Version** | IPv4+IPv6 |
| **Protocol** | any |
| **Source** | InternalNetworks |
| **Destination / Invert** | Invert the sense of the match |
| **Destination** | InternalNetworks |
| **Destination port range** | from: any - to: any |
| **Log** | Log packets |
| **Category** | Internet |
| **Description** | Internet |
Enfin, je veux autoriser tout depuis mes réseaux fiables. Dans `Firewall` > `Rules` > `Trusted`, je crée la règle :
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Trusted |
| **Direction** | in |
| **TCP/IP Version** | IPv4+IPv6 |
| **Protocol** | any |
| **Source** | Trusted net |
| **Destination** | any |
| **Destination port range** | from: any - to: any |
| **Log** | Log packets |
| **Category** | Trusted |
| **Description** | Trusted |
Parfait, avec ces 3 règles, je couvre les bases. Les règles restantes serviront à autoriser certains équipements à atteindre d'autres services. Par exemple, mon instance Home Assistant veut se connecter à ma TV, les deux sont sur des VLANs différents, donc j'ai besoin d'une règle pour l'autoriser :
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Lab |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | TCP |
| **Source** | 192.168.66.50/32 |
| **Destination** | 192.168.37.30/32 |
| **Destination port range** | from: 3000 - to: 3001 |
| **Log** | Log packets |
| **Category** | Home Assistant |
| **Description** | Home assistant to TV |
---
## DHCP
Pour le DHCP, je choisis **Dnsmasq**. Dans mon installation actuelle j'utilise ISC DHCPv4, mais comme il est maintenant déprécié, je préfère le remplacer. Dnsmasq agira aussi comme DNS, mais seulement pour mes zones locales.
Dnsmasq ne synchronise pas les baux. Pour éviter les conflits, les deux nœuds servent le DHCP mais avec un délai de réponse décalé et des plages différentes. Le master couvre le pool principal, le backup un petit pool de secours.
### Configuration Générale de Dnsmasq
Dans `Services` > `Dnsmasq DNS & DHCP` > `General`, je configure le parefeu master comme suit :
- **Default**
- **Enable**: Oui
- **Interface**: *Mgmt*, *User*, *IoT*, *DMZ* et *Lab*
- **DNS**
- **Listen port**: 53053
- **DNS Query Forwarding**
- **Do not forward to system defined DNS servers**: Activé
- **DHCP**
- **DHCP FQDN**: Activé
- **DHCP local domain**: Activé
- **DHCP authoritative**: Activé
- **DHCP reply delay**: 0
- **DHCP register firewall rules**: Activé
- **Disable HA sync**: Activé
Sur le nœud backup, je le configure de la même manière, la seule différence sera le **DHCP reply delay** que je règle à **10**. Cela donne au master le temps de répondre aux requêtes DHCP avant que le backup n'intervienne.
### Plages DHCP
Ensuite je configure les plages DHCP. Les deux parefeu auront des plages différentes, le nœud backup aura des plages plus petites (10 baux devraient suffire). Sur le master, elles sont configurées comme suit :
![OPNsense DHCP ranges in Dnsmasq](img/opnsense-dnsmasq-dhcp-ranges.png)
### Options DHCP
Puis je définis quelques options DHCP pour chaque domaine : le `router`, le `dns-server` et le `domain-name`. Je pointe les adresses IP vers la VIP de l'interface :
![OPNsense DHCP options in Dnsmasq](img/opnsense-dnsmasq-dhcp-options.png)
### Hôtes
Enfin, dans l'onglet `Hosts`, je définis des mappings DHCP statiques mais aussi des IP statiques non gérées par le DHCP, pour qu'elles soient enregistrées dans le DNS :
![Hôtes DHCP de Dnsmasq dans OPNsense](img/opnsense-dnsmasq-dhcp-hosts.png)
---
## DNS
Pour le DNS, j'utilise **Unbound**. C'est un résolveur DNS récursif, validant et cacheur intégré à OPNsense, qui peut :
- Résoudre les requêtes depuis les serveurs racine.
- Mettre en cache les résultats pour des réponses plus rapides.
- Vérifier l'authenticité des domaines avec DNSSEC.
- Bloquer des domaines via une liste noire.
- Ajouter des enregistrements personnalisés.
Unbound est le résolveur récursif, pour les zones locales j'effectue un forward vers Dnsmasq.
### Paramètres Généraux d'Unbound
Configurons-le, dans `Services` > `Unbound DNS` > `General` :
![OPNsense Unbound DNS general settings](img/opnsense-unbound-general-settings.png)
### Liste de Blocage DNS
Ensuite je configure la blocklist dans `Services` > `Unbound DNS` > `Blocklist`. Je l'active et sélectionne la liste `[hagezi] Multi PRO mini`. Initialement j'utilisais AdGuard Home, mais je veux donner une chance à cette fonctionnalité de blocklist.
Pour maintenir le service à jour, dans `System` > `Settings` > `Cron`, j'ajoute mon premier job qui s'exécute chaque nuit à 2h pour `Update Unbound DNSBLs`.
### Transfert de Requêtes
Enfin je configure le transfert de requêtes pour mes domaines locaux vers Dnsmasq. Dans `Services` > `Unbound DNS` > `Query Forwarding`, j'ajoute chacun de mes domaines locaux avec leurs reverse lookups (enregistrements PTR) :
![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](img/opnsense-unbound-dns-query-forwarding.png)
---
## VPN
Quand je ne suis pas chez moi, je veux toujours pouvoir atteindre mes services et profiter de mon bloqueur DNS. Pour cela je configure un VPN avec **WireGuard**. C'est rapide, sécurisé et simple à mettre en place.
### Configuration de l'Instance WireGuard
Dans `VPN` > `WireGuard` > `Instances`, je crée une nouvelle instance :
- **Enabled**: Oui
- **Name**: *Homelan*
- **Public/Private keys**: Paire de clés générée
- **Listen port**: `61337`
- **Tunnel address**: `10.13.37.1/24`
- **Depend on (CARP)**: on *lan* (vhid 1)
Une fois configuré, j'active WireGuard et j'applique la configuration.
### Configuration d'un Peer
Ensuite, dans l'onglet `Peer generator`, je remplis les champs pour mon premier appareil :
- **Endpoint** : `vezpi.com`
- **Name** : _S25Ultra_
- **DNS Servers** : `10.13.37.1`
Avant de cliquer sur `Store and generate next`, depuis mon appareil je configure le peer en capturant le QR code. Enfin je peux sauvegarder ce peer et recommencer pour les suivants.
### Créer l'Interface VPN
Cette étape n'est pas obligatoire, mais facilite la gestion des règles de parefeu. Sur les deux parefeu, dans `Interfaces` > `Assignments`, j'assigne l'interface `wg0 (WireGuard - Homelan)` et la nomme _VPN_.
Puis dans `Interfaces` > `VPN`, j'active cette interface.
Enfin, dans `Firewall` > `Groups`, j'ajoute cette interface au groupe _Trusted_.
### Règle de Pare-feu
Pour autoriser les connexions depuis l'extérieur, je dois créer une règle de parefeu sur l'interface WAN :
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | WAN |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | UDP |
| **Source** | any |
| **Destination** | WAN address |
| **Destination port range** | from: 61337 - to: 61337 |
| **Log** | Log packets |
| **Category** | VPN |
| **Description** | WireGuard |
---
## Reverse Proxy
La fonctionnalité suivante dont jai besoin est un **reverse proxy**, pour rediriger les requêtes HTTPS entrantes vers mes services, comme ce blog par exemple.
Pour cela, jutilise **Caddy**. Il écoute sur les ports 80 et 443, cest la raison pour laquelle jai déplacé linterface WebGUI dOPNsense vers dautres ports au début.
Ce service nest pas installé par défaut, il faut ajouter un plugin.
Sur les deux pare-feux, dans `System` > `Firmware` > `Plugins`, je coche loption pour afficher les plugins communautaires et jinstalle **`os-caddy`**.
### Paramètres Généraux de Caddy
Je rafraîchis ensuite la page et, sur le nœud maître, je vais dans `Services` > `Caddy` > `General Settings` :
- **Enable Caddy** : Oui
- **Enable Layer4 Proxy** : Oui
- **ACME** : `<adresse email>`
- **Auto HTTPS** : On (default)
Il existe deux types de redirections : le `Reverse Proxy` et le `Layer4 Proxy`. Le premier est réservé au HTTPS, où Caddy gère le chiffrement SSL lui-même.
### Reverse Proxy
Dans `Services` > `Caddy` > `Reverse Proxy`, je définis les services directement gérés par Caddy.
Ces services ne doivent pas être accessibles à tout le monde.
Dans longlet **Access**, je crée une liste appelée `Internal`, qui regroupe mes réseaux autorisés, notamment le LAN et le VPN.
Puis, dans longlet `Domains`, jajoute mes domaines. Par exemple, cest ici que je définis `cerbere.vezpi.com`, lURL pour accéder à mon interface WebGUI OPNsense :
- **Enable** : Oui
- **Frontend**
- **Protocol** : `https://`
- **Domain** : `cerbere.vezpi.com`
- **Port** : laisser vide
- **Certificate** : Auto HTTPS
- **HTTP-01 Challenge Redirection** : `192.168.44.2` (pour la haute disponibilité)
- **Description** : OPNsense
- **Access**
- **Access List** : `Internal`
- **HTTP Access Log** : Activé
Enfin, dans longlet `Handlers`, je définis la destination vers laquelle ces domaines doivent être redirigés. Pour `cerbere.vezpi.com`,
jajoute :
- **Enabled** : Oui
- **Frontend**
- **Domain** : `https://cerbere.vezpi.com`
- **Subdomain** : None
- **Handler**
- **Path** : any
- **Access**
- **Access List** : None
- **Directive**
- **Directive** : `reverse_proxy`
- **Upstream**
- **Protocol** : `https://`
- **Upstream Domain** : `127.0.0.1`
- **Upstream Port** : `4443`
- **TLS Insecure Skip Verify** : Activé
- **Description** : OPNsense
### Layer4 Proxy
La plupart de mes services sont déjà derrière un autre reverse proxy sur mon réseau, **Traefik**. Pour lui permettre de gérer directement ses domaines, je les redirige à laide de `Layer4 Routes`. Cela évite que Caddy termine le SSL, le flux HTTPS reste donc intact.
Dans `Services` > `Caddy` > `Layer4 Proxy`, je crée 3 routes.
La première concerne les services exposés sur Internet, comme ce blog ou mon instance Gitea :
- **Enabled** : Oui
- **Sequence** : 1
- **Layer 4**
- **Routing Type** : listener_wrappers
- **Layer 7**
- **Matchers** : TLS (SNI Client Hello)
- **Domain** : `blog.vezpi.com` `git.vezpi.com`
- **Terminate SSL** : No
- **Upstream**
- **Upstream Domain** : `192.168.66.50`
- **Upstream Port** : `443`
- **Proxy Protocol** : `v2` (si supporté par lupstream)
- **Description** : External Traefik HTTPS dockerVM
La deuxième route est dédiée aux services internes uniquement. Elle est configurée de manière similaire, mais avec une liste de contrôle daccès :
- **Sequence** : 2
- **Access**
- **Remote IP** : `192.168.13.0/24` `192.168.88.0/24` `10.13.37.0/24`
La troisième route gère les challenge HTTP de Traefik pour Let's Encrypt :
- **Sequence** : 3
- **Layer 7**
- **Matchers** : HTTP (Host Header)
- **Domain** : `blog.vezpi.com` `git.vezpi.com` etc.
- **Upstream**
- **Upstream Port** : 80
- **Proxy Protocol** : Off (default)
### Règles du pare-feu
Enfin, je dois autoriser les connexions sur ces ports sur le pare-feu.
Je crée donc une règle pour le HTTPS (et une autre pour le HTTP) :
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | WAN |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | TCP |
| **Source** | any |
| **Destination** | WAN address |
| **Destination port range** | from: HTTPS - to: HTTPS |
| **Log** | Log packets |
| **Category** | Caddy |
| **Description** | Caddy HTTPS |
---
## Répéteur mDNS
Le dernier service que je souhaite configurer dans OPNsense est un répéteur mDNS. Il est utile pour certains appareils qui annoncent leur présence sur le réseau, lorsquils ne sont pas sur le même VLAN, comme mon imprimante ou mon Chromecast. Le répéteur mDNS reçoit les annonces dune interface et les transmet sur une autre.
Ce service nest pas installé par défaut. Sur les deux pare-feux, dans `System` > `Firmware` > `Plugins`, je coche loption pour afficher les plugins communautaires et jinstalle **`os-mdns-repeater`**.
Ensuite, dans `Services` > `mDNS Repeater`, la configuration est très simple :
- **Enable** : Oui
- **Enable CARP Failover** : Oui
- **Listen Interfaces** : _IoT_, _User_
---
## Synchronisation des services
La dernière étape consiste à synchroniser tous les services entre le maître et le nœud de secours du cluster. Dabord, dans `System` > `High Availability` > `Status`, je clique sur le bouton **Synchronize and reconfigure all**.
Puis, je veux massurer que les futurs changements soient synchronisés même si joublie de les répliquer. Dans `System` > `Settings` > `Cron`, jajoute une tâche planifiée chaque nuit pour **HA update and reconfigure backup**.
---
## Conclusion
🚀 Mon cluster **OPNsense** est désormais configuré et prêt à être mis en production !
Jespère que cette configuration complète dOPNsense pour mon propre réseau taidera à construire la tienne.
La prochaine étape sera de planifier la migration depuis mon ancienne box OPNsense vers ces deux pare-feu en haute disponibilité.
Cette nouvelle infrastructure renforcera la sécurité de mes opérations tout en éliminant le SPOF (Single Point Of Failure) de mon réseau.
À très vite pour te raconter comment cette opération sest déroulée !

View File

@@ -0,0 +1,579 @@
---
slug: opnsense-ha-full-configuration
title: OPNsense HA in Proxmox, my Full Homelab Configuration
description: Step-by-step OPNsense HA cluster full configuration in Proxmox, interfaces and VIP, firewall, Dnsmasq DHCP, Unbound DNS, WireGuard VPN and Caddy reverse proxy.
date: 2025-10-23
draft: false
tags:
- opnsense
- high-availability
- proxmox
- unbound-dns
- caddy
- wireguard
- dnsmasq
categories:
- homelab
---
## Intro
In my previous [article]({{< ref "post/12-opnsense-virtualization-highly-available" >}}) I set up a PoC to validate building a cluster of two **OPNsense** VMs in **Proxmox VE** to make the firewall highly available.
Now I'm preparing to make that real in my homelab, this post documents my real OPNsense cluster configuration, from fresh installs to HA, DNS, DHCP, VPN and reverse proxy.
### Context
Before diving into the OPNsense configuration, a little bit of context to understand the choices I made.
In my Proxmox VE cluster, I've created 2 VMs and installed OPNsense. The goal is to replace my single physical box by this cluster. Each VM have 7 NICs for the following networks:
- **vmbr0**: *Mgmt*
- **vlan20**: *WAN*
- **vlan13**: *User*
- **vlan37**: *IoT*
- **vlan44**: *pfSync*
- **vlan55**: *DMZ*
- **vlan66**: *Lab*
Initially I was thinking of just restoring my current configuration on the VM freshly installed. But then I realized that I didn't really documented how I put the pieces together the first time. This is the perfect moment to put things right.
⚠️ I can only have a single WAN IP, shared between the nodes, served by the DHCP of my ISP box. For this reason I won't have a VIP for the WAN and I have to find a solution to share this single IP.
Hopefully in the next post, if this project lands on my production network, I will also cover the VM creation on Proxmox and how I'm preparing this migration from my physical OPNsense box to this highly available cluster in VMs. Let's dive in!
---
## System
### General
I start with the basics, in `System` > `Settings` > `General`:
- **Hostname**: `cerbere-head1` (`cerbere-head2` for the second one).
- **Domain**: `mgmt.vezpi.com`.
- **Time zone**: `Europe/Paris`.
- **Language**: `English`.
- **Theme**: `opnsense-dark`.
- **Prefer IPv4 over IPv6**: tick the box to prefer IPv4.
### Users
Then, in `System` > `Access` > `Users`, I create a new user rather than using `root`, add it to the `admins` group, and remove `root` from that group.
### Administration
In `System` > `Settings` > `Administration`, I change several things:
- **Web GUI**
- **TCP port**: from `443` to `4443`, to free port 443 for the reverse proxy coming next.
- **HTTP Redirect**: Disabled, to free port 80 for the reverse proxy
- **Alternate Hostnames**: `cerbere.vezpi.com` which will be the URL to reach the firewall by the reverse proxy.
- **Access log**: Enabled.
- **Secure Shell**
- **Secure Shell Server**: Enabled.
- **Root Login**: Disabled.
- **Authentication Method:** Permit password login (no `root` login).
- **Listen Interfaces**: *Mgmt*
- **Authentication**
- **Sudo**: `No password`.
Once I click `Save`, I follow the link given to reach the WebGUI on port `4443`.
### Updates
Time for updates, in `System` > `Firmware` > `Status`, I check for firmware updates and apply them (requires reboot).
### QEMU Guest Agent
Once updated and rebooted, I go to `System` > `Firmware` > `Plugins`, I tick the box to show community plugins. I install the **QEMU Guest Agent**, `os-qemu-guest-agent`, to allow communication between the VM and the Proxmox host.
This requires a shutdown. On Proxmox, I enable the `QEMU Guest Agent` in the VM options:
Proxmox VM options with QEMU Guest Agent enabled
Finally I restart the VM. Once started, from the Proxmox WebGUI, I can see the IPs of the VM which confirms the guest agent is working.
---
## Interfaces
On both firewalls, I assign the remaining NICs to new interfaces adding a description. The VMs have 7 interfaces, I carefully compare MAC addresses to avoid mixing interfaces:
![Assign interfaces menu in OPNsense](img/opnsense-assign-interfaces.png)
In the end, the interfaces configuration looks like this:
| Interface | Mode | `cerbere-head1` | `cerbere-head2` |
| --------- | ----------- | --------------- | --------------- |
| *Mgmt* | Static IPv4 | 192.168.88.2/24 | 192.168.88.3/24 |
| *WAN* | DHCPv4/6 | Enabled | Disabled |
| *User* | Static IPv4 | 192.168.13.2/24 | 192.168.13.3/24 |
| *IoT* | Static IPv4 | 192.168.37.2/24 | 192.168.37.3/24 |
| *pfSync* | Static IPv4 | 192.168.44.1/30 | 192.168.44.2/30 |
| *DMZ* | Static IPv4 | 192.168.55.2/24 | 192.168.55.3/24 |
| *Lab* | Static IPv4 | 192.168.66.2/24 | 192.168.66.3/24 |
I don't configure Virtual IPs yet, I'll manage that once high availability has been setup.
---
## High Availability
### Firewall Rule for pfSync
From here we can associate both instances to create a cluster. The last thing I need to do, is to allow the communication on the *pfSync* interface. By default, no communication is allowed on the new interfaces.
From `Firewall` > `Rules` > `pfSync`, I create a new rule on each firewall:
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | *pfSync* |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | any |
| **Source** | *pfSync* net |
| **Destination** | *pfSync* net |
| **Destination port range** | from: any - to: any |
| **Log** | Log packets |
| **Category** | OPNsense |
| **Description** | pfSync |
### Configure HA
OPNsense HA uses pfSync for firewall state synchronization (real-time) and XMLRPC Sync to push config and services from master → backup (one-way).
The HA is setup in `System` > `High Availability` > `Settings`
#### Master
- **General Settings**
- **Synchronize all states via**: *pfSync*
- **Synchronize Peer IP**: `192.168.44.2`, the backup node IP
- **Configuration Synchronization Settings (XMLRPC Sync)**
- **Synchronize Config**: `192.168.44.2`
- **Remote System Username**: `<username>`
- **Remote System Password**: `<password>`
- **Services to synchronize (XMLRPC Sync)**
- **Services**: Select All
#### Backup (`cerbere-head2`):
- **General Settings**
- **Synchronize all states via**: *pfSync*
- **Synchronize Peer IP**: `192.168.44.1`, the master node IP
⚠️ Do not fill the XMLRPC Sync fields on the backup node, only to be filled on the master.
### HA Status
In the section `System` > `High Availability` > `Status`, I can verify if the synchronization is working. On this page I can replicate any or all services from my master to my backup node:
![OPNsense high availability status page](img/opnsense-high-availability-status.png)
---
## Virtual IPs
Now that HA is configured, I can give my networks a virtual IP shared across my nodes. In `Interfaces` > `Virtual IPs` > `Settings`, I create one VIP for each of my networks using **CARP** (Common Address Redundancy Protocol). The target is to reuse the IP addresses used by my current OPNsense instance, but as it is still routing my network, I use different IPs for the configuration phase:
![Liste des IPs virtuelles dans OPNsense](img/opnsense-interface-virtual-ips.png)
OPNsense allows CARP by default, no special firewall rule required
---
## CARP Failover Script
In my setup, I only have a single WAN IP address which is served by the DHCP of my ISP box. OPNsense does not provide natively a way to handle this scenario. To manage it, I implement the same trick I used in the [PoC]({{< ref "post/12-opnsense-virtualization-highly-available" >}}).
### Copy MAC Address
I copy the MAC of the `net1` interface of `cerbere-head1` and paste it to the same interface for `cerbere-head2`. Doing so, the DHCP lease for the WAN IP address can be shared among the nodes.
⚠️ Warning: Having two machines on the network with the same MAC can cause ARP conflicts and break connectivity. Only one VM should keep its interface active.
### CARP Event Script
Under the hood, in OPNsense, a CARP event triggers some scripts (when the master dies). These are located in `/usr/local/etc/rc.syshook.d/carp/`.
To manage WAN interface on each node, I implement this PHP script `10-wan` on both nodes, using SSH (do not forget to make it executable). Depending on their role (master or backup), this will enable or disable their WAN interface:
```php
#!/usr/local/bin/php
<?php
require_once("config.inc");
require_once("interfaces.inc");
require_once("util.inc");
require_once("system.inc");
$subsystem = !empty($argv[1]) ? $argv[1] : '';
$type = !empty($argv[2]) ? $argv[2] : '';
if ($type != 'MASTER' && $type != 'BACKUP') {
log_error("Carp '$type' event unknown from source '{$subsystem}'");
exit(1);
}
if (!strstr($subsystem, '@')) {
log_error("Carp '$type' event triggered from wrong source '{$subsystem}'");
exit(1);
}
$ifkey = 'wan';
if ($type === "MASTER") {
log_error("enable interface '$ifkey' due CARP event '$type'");
$config['interfaces'][$ifkey]['enable'] = '1';
write_config("enable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
} else {
log_error("disable interface '$ifkey' due CARP event '$type'");
unset($config['interfaces'][$ifkey]['enable']);
write_config("disable interface '$ifkey' due CARP event '$type'", false);
interface_configure(false, $ifkey, false, false);
}
```
In `Interfaces` > `Virtual IPs` > `Status`, I can force a CARP event by entering into `Persistent maintenance mode`. Triggering this allow me to test this script, which disable the WAN interface on the master while enabling it on the backup.
---
## Firewall
Let's configure the core feature of OPNsense, the firewall. I don't want to go too crazy with the rules. I only need to configure the master, thanks to the replication.
### Interface Groups
Basically I have 2 kinds of networks, those which I trust, and those which I don't. From this standpoint, I will create two zones.
Globally, my untrusted networks only have access to DNS and the internet. Trusted networks can reach other VLANs.
To begin, in `Firewall` > `Groups`, I create 2 zones to regroup my interfaces:
- **Trusted**: *Mgmt*, *User*
- **Untrusted**: *IoT*, *DMZ*, *Lab*
### Network Aliases
Next, in `Firewall` > `Aliases`, I create an alias `InternalNetworks` to regroup all my internal networks:
![Création d'alias pour les réseaux locaux dansOPNsense](img/opnsense-create-alias-internalnetworks.png)
### Firewall Rules
For all my networks, I want to allow DNS queries on the local DNS. In `Firewall` > `Rules` > `Floating`, let's create the first rule:
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Trusted, Untrusted |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | TCP/UDP |
| **Source** | InternalNetworks |
| **Destination** | This Firewall |
| **Destination port range** | from: DNS - to: DNS |
| **Log** | Log packets |
| **Category** | DNS |
| **Description** | DNS query |
Next I want to allow connections towards the internet. At the same place I create a second rule:
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Trusted, Untrusted |
| **Direction** | in |
| **TCP/IP Version** | IPv4+IPv6 |
| **Protocol** | any |
| **Source** | InternalNetworks |
| **Destination / Invert** | Invert the sense of the match |
| **Destination** | InternalNetworks |
| **Destination port range** | from: any - to: any |
| **Log** | Log packets |
| **Category** | Internet |
| **Description** | Internet |
Finally, I want to allow anything from my trusted networks. In `Firewall` > `Rules` > `Trusted`, I create the rule:
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Trusted |
| **Direction** | in |
| **TCP/IP Version** | IPv4+IPv6 |
| **Protocol** | any |
| **Source** | Trusted net |
| **Destination** | any |
| **Destination port range** | from: any - to: any |
| **Log** | Log packets |
| **Category** | Trusted |
| **Description** | Trusted |
Great, with these 3 rules, I cover the basics. The remaining rules would be to allow specific equipment to reach out to something else. For example my home assistant instance want to connect to my TV, both are on different VLAN, hence I need a rule to allow it:
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | Lab |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | TCP |
| **Source** | 192.168.66.50/32 |
| **Destination** | 192.168.37.30/32 |
| **Destination port range** | from: 3000 - to: 3001 |
| **Log** | Log packets |
| **Category** | Home Assistant |
| **Description** | Home assistant to TV |
---
## DHCP
For the DHCP, I choose **Dnsmasq**. In my current installation I use ISC DHCPv4, but as it is now deprecated, I prefer to replace it. Dnsmasq will also act as DNS, but only for my local zones.
Dnsmasq doesn't sync leases. To avoid conflicts, both nodes serve DHCP but with staggered reply delay and different ranges. The master covers the main pool, the backup a small fallback pool
### Dnsmasq General Configuration
In `Services` > `Dnsmasq DNS & DHCP` > `General`, I configure the master firewall as follow:
- **Default**
- **Enable**: Yes
- **Interface**: *Mgmt*, *User*, *IoT*, *DMZ* and *Lab*
- **DNS**
- **Listen port**: 53053
- **DNS Query Forwarding**
- **Do not forward to system defined DNS servers**: Enabled
- **DHCP**
- **DHCP FQDN**: Enabled
- **DHCP local domain**: Enabled
- **DHCP authoritative**: Enabled
- **DHCP reply delay**: 0
- **DHCP register firewall rules**: Enabled
- **Disable HA sync**: Enabled
On the backup node, I configure it the same, the only difference will be the **DHCP reply delay** which I set to **10**. This gives the master time to answer DHCP requests before the backup responds.
### DHCP Ranges
Next I configure the DHCP ranges. Both firewalls will have different ranges, the backup node will have smaller ones (only 10 leases should be enough). On the master, they are configured as follow:
![OPNsense DHCP ranges in Dnsmasq](img/opnsense-dnsmasq-dhcp-ranges.png)
### DHCP Options
Then I set some DHCP options for each domain: the `router`, the `dns-server` and the `domain-name`. I'm pointing the IP addresses to the interface's VIP:
![OPNsense DHCP options in Dnsmasq](img/opnsense-dnsmasq-dhcp-options.png)
### Hosts
Finally in in the `Hosts` tab, I define static DHCP mappings but also static IP not managed by the DHCP, to have them registered in the DNS:
![Hôtes DHCP de Dnsmasq dans OPNsense](img/opnsense-dnsmasq-dhcp-hosts.png)
---
## DNS
For the DNS, I use **Unbound**. It is a validating, recursive, caching DNS resolver built into OPNsense, which can:
- Resolve queries from the root servers.
- Cache results for faster responses.
- Check domain authenticity with DNSSEC.
- Block domains based on a blacklist.
- Add custom records.
Unbound is the recursive resolver, for local zones I forward queries to Dnsmasq.
### Unbound General Settings
Let's configure it, in `Services` > `Unbound DNS` > `General`:
![OPNsense Unbound DNS general settings](img/opnsense-unbound-general-settings.png)
### DNS Blocklist
Then I configure the blocklist in `Services` > `Unbound DNS` > `Blocklist`. I enable it and select the `[hagezi] Multi PRO mini` list. Initially I was using AdGuard Home, but I want to give this blocklist feature a chance.
To maintain the service up to date, in `System` > `Settings` > `Cron`, I add my first job that runs every night at 2AM to `Update Unbound DNSBLs`.
### Query Forwarding
Finally I configure query forwarding for my local domains to Dnsmasq. In `Services` > `Unbound DNS` > `Query Forwarding`, I add each of my local domains with their reverse lookup (PTR record):
![Configuration du transfert de requêtes d'Unbound DNS dans OPNsense](img/opnsense-unbound-dns-query-forwarding.png)
---
## VPN
When I'm not home, I still want to be able to reach my services and enjoy my DNS ad blocker. For that I'm setting up a VPN, with **WireGuard**. It's fast, secure and easy to set up.
### WireGuard Instance Setup
In `VPN` > `WireGuard` > `Instances`, I create a new one:
- **Enabled**: Yes
- **Name**: *Homelan*
- **Public/Private keys**: Key-pair generated
- **Listen port**: `61337`
- **Tunnel address**: `10.13.37.1/24`
- **Depend on (CARP)**: on *lan* (vhid 1)
Once configured, I enable WireGuard and apply the configuration.
### Peer Setup
Next in the `Peer generator` tab, I fulfill the empty fields for my first device:
- **Endpoint**: `vezpi.com`
- **Name**: *S25Ultra*
- **DNS Servers**: `10.13.37.1`
Before clicking `Store and generate next`, from my device, I configure the peer by capturing the QR code. Finally I can save that peer and start over for new ones.
### Create VPN Interface
This step is not required, but ease the configuration management for firewall rules. On both firewall, in `Interfaces` > `Assignments`, I assign the `wg0 (WireGuard - Homelan)` interface and name it *VPN*.
Then in `Interfaces` > `VPN`, I enable this interface.
Finally, in `Firewall` > `Groups`, I add this interface in the *Trusted* group.
### Firewall Rule
To allow connections from outside, I need to create a firewall rule on the WAN interface:
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | WAN |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | UDP |
| **Source** | any |
| **Destination** | WAN address |
| **Destination port range** | from: 61337 - to: 61337 |
| **Log** | Log packets |
| **Category** | VPN |
| **Description** | WireGuard |
---
## Reverse Proxy
The next feature I need is a reverse proxy, to redirect incoming HTTPS requests to reach my services, such as this blog. For that I use **Caddy**. It will listen on port 80/443, that's why I moved the WebGUI off these ports at the beginning.
This service is not installed by default, I need to add a plugin. On both firewalls, In `System` > `Firmware` > `Plugins`, I tick the box to show community plugins and install `os-caddy`.
### Caddy General Settings
I refresh the page and, on the master, in `Services` > `Caddy` > `General Settings`:
- **Enable Caddy**: Yes
- **Enable Layer4 Proxy**: Yes
- **ACME**: `<email address>`
- **Auto HTTPS**: On (default)
There are two types of redirections, the `Reverse Proxy` and the `Layer4 Proxy`. The first one is for HTTPS only, where Caddy will manage the SSL.
### Reverse Proxy
In `Services` > `Caddy` > `Reverse Proxy`, I define the services directly managed by Caddy.
These services should not be exposed to everyone. In the `Access` tab, I create a list, called `Internal`, of allowed networks, including my LAN and VPN networks.
Then in the `Domains` tab, I add my domains. For example, this is here I define `cerbere.vezpi.com`, my URL to reach my OPNsense WebGUI:
- **Enable**: Yes
- **Frontend**
- **Protocol**: `https://`
- **Domain**: `cerbere.vezpi.com`
- **Port**: leave empty
- **Certificate**: Auto HTTPS
- **HTTP-01 Challenge Redirection**: `192.168.44.2`, for high availability setup
- **Description**: OPNsense
- **Access**
- **Access List**: `Internal`
- **HTTP Access Log**: Enabled
Finally in the `Handlers` tab, I define to which upstream these domains are forwarded to. For `cerbere.vezpi.com` I define this:
- **Enabled**: Yes
- **Frontend**
- **Domain**: `https://cerbere.vezpi.com`
- **Subdomain**: None
- **Handler**
- **Path**: any
- **Access**
- **Access List**: None
- **Directive**
- **Directive**: `reverse_proxy`
- **Upstream**
- **Protocol**: `https://`
- **Upstream Domain**: `127.0.0.1`
- **Upstream Port**: `4443`
- **TLS Insecure Skip Verify**: Enabled
- **Description**: OPNSense
### Layer4 Proxy
Most of my services are behind another reverse proxy on my network, Traefik. To let it manage normally its domains, I forward them using `Layer4 Routes`. It prevents Caddy to terminate SSL, the HTTPS stream is left intact.
In `Services` > `Caddy` > `Layer4 Proxy`, I create 3 routes.
The first one is for internet exposed services, like this blog or my Gitea instance:
- Enabled: Yes
- Sequence: 1
- Layer 4
- Routing Type: listener_wrappers
- Layer 7
- Matchers: TLS (SNI Client Hello)
- Domain: `blog.vezpi.com` `git.vezpi.com`
- Terminate SSL: No
- Upstream
- Upstream Domain: `192.168.66.50`
- Upstream Port: `443`
- Proxy Protocol: `v2`, if your upstream supports it
- Description: External Traefik HTTPS dockerVM
The second one is for internal only services. It is configured pretty much the same but using an access list:
- Sequence: 2
- Access
- Remote IP: `192.168.13.0/24` `192.168.88.0/24` `10.13.37.0/24`
The third one is for Traefik HTTP challenges for Let's Encrypt:
- Sequence: 3
- Layer 7
- Matchers: HTTP (Host Header)
- Domain: `blog.vezpi.com` `git.vezpi.com` etc.
- Upstream:
- Upstream Port: 80
- Proxy Protocol: Off (default)
### Firewall Rules
Finally, I need to allow connection of these ports on the firewall, I create one rule for HTTPS (and another for HTTP):
| Field | Value |
| -------------------------- | ------------------------------------- |
| **Action** | Pass |
| **Quick** | Apply the action immediately on match |
| **Interface** | WAN |
| **Direction** | in |
| **TCP/IP Version** | IPv4 |
| **Protocol** | TCP |
| **Source** | any |
| **Destination** | WAN address |
| **Destination port range** | from: HTTPS - to: HTTPS |
| **Log** | Log packets |
| **Category** | Caddy |
| **Description** | Caddy HTTPS |
---
## mDNS Repeater
The last service I want to setup in OPNsense is a mDNS repeater. This is useful for some devices to announce themselves on the network, when not on the same VLAN, such as my printer or my Chromecast. The mDNS repeater get the message from an interface to forward it to another one.
This service is also not installed by default. On both firewalls, In `System` > `Firmware` > `Plugins`, I tick the box to show community plugins and install `os-mdns-repeater`.
Then in `Services` > `mDNS Repeater`, the configuration is pretty straight forward:
- Enable: Yes
- Enable CARP Failover: Yes
- Listen Interfaces: *IoT*, *User*
---
## Service Synchronization
The final step is to synchronize all the services between the master and the backup node in the cluster. First in `System` > `High Availability` > `Status`, I click the button to `Synchronize and reconfigure all`.
Then I want to make sure that future changes are synchronized if I omit to replicate them myself. In `System` > `Settings` > `Cron`, I add a new job that runs every night to `HA update and reconfigure backup`.
---
## Conclusion
🚀 My **OPNsense** cluster is now configured and ready to go live!
I hope this OPNsense full configuration for my own network, help you with your own setup.
The next phase is to plan the migration from my current OPNsense box to these two highly available firewalls. This new infrastructure would secure my future operations on the firewall, while removing this SPOF (Single Point Of Failure) of my network.
See you next time to tell how this operation went!