Compare commits

16 Commits

Author SHA1 Message Date
e41c25b64f feat: secure docker image removal
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 35s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-05-25 13:47:56 +00:00
ce1c7e36bb feat: reduce the sleep to 30s
Some checks failed
Blog Test / Check-Rebuild (push) Successful in 6s
Blog Test / Build (push) Successful in 7s
Blog Test / Deploy-Test (push) Successful in 36s
Blog Test / Test (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 5s
Blog Deployment / Build (push) Successful in 7s
Blog Deployment / Deploy-Staging (push) Successful in 35s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 34s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Failing after 2s
Blog Deployment / Notify (push) Successful in 3s
2026-05-25 13:30:26 +00:00
6a4fdcb6ff fix: corrected new sleep timer to 60 for test pipeline
All checks were successful
Blog Test / Check-Rebuild (push) Successful in 6s
Blog Test / Build (push) Successful in 8s
Blog Test / Deploy-Test (push) Successful in 1m3s
Blog Test / Test (push) Successful in 2s
2026-05-25 13:27:35 +00:00
07ce7c58ef feat: remove deprecation workaround
Some checks failed
Blog Test / Check-Rebuild (push) Successful in 6s
Blog Test / Build (push) Successful in 36s
Blog Test / Deploy-Test (push) Successful in 8s
Blog Test / Test (push) Failing after 3s
2026-05-25 13:18:04 +00:00
Gitea Actions
bd121d794c Auto-update blog content from Obsidian: 2026-05-24 21:07:44
Some checks failed
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m3s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 1m3s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Test / Check-Rebuild (push) Successful in 7s
Blog Test / Build (push) Has been skipped
Blog Test / Deploy-Test (push) Failing after 4s
Blog Test / Test (push) Has been skipped
2026-05-24 21:07:44 +00:00
Gitea Actions
19258d081c Auto-update blog content from Obsidian: 2026-05-24 20:58:30
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-05-24 20:58:30 +00:00
Gitea Actions
40ec16e974 Auto-update blog content from Obsidian: 2026-05-24 20:12:49
All checks were successful
Blog Deployment / Merge (push) Successful in 6s
Blog Deployment / Deploy-Production (push) Successful in 1m3s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m3s
Blog Deployment / Test-Staging (push) Successful in 2s
2026-05-24 20:12:49 +00:00
Gitea Actions
8e4e4601d7 Auto-update blog content from Obsidian: 2026-05-24 19:12:42
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 7s
Blog Deployment / Deploy-Production (push) Successful in 1m5s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 19:12:42 +00:00
Gitea Actions
8facd6010b Auto-update blog content from Obsidian: 2026-05-24 14:31:31
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 14:31:31 +00:00
Gitea Actions
49631bbabc Auto-update blog content from Obsidian: 2026-05-24 13:31:24
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 1m5s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 13:31:24 +00:00
Gitea Actions
0e81ddf7ed Auto-update blog content from Obsidian: 2026-05-24 12:30:51
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m5s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
2026-05-24 12:30:51 +00:00
a01f4dcf4e fix: typo
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 8s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 1m4s
Blog Deployment / Test-Staging (push) Successful in 2s
Blog Deployment / Merge (push) Successful in 9s
Blog Deployment / Deploy-Production (push) Successful in 1m4s
Blog Deployment / Test-Production (push) Successful in 2s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 2s
2026-05-02 17:09:10 +00:00
8d88e5c87f change: wait 60s before showing logs
All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Build (push) Successful in 7s
Blog Deployment / Deploy-Staging (push) Successful in 1m3s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 1m3s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Successful in 2s
Blog Deployment / Notify (push) Successful in 2s
2026-04-30 10:52:02 +00:00
7728af1cdb change: wait 15s before showing logs
Some checks failed
Blog Deployment / Check-Rebuild (push) Successful in 6s
Blog Deployment / Deploy-Staging (push) Successful in 9s
Blog Deployment / Build (push) Successful in 7s
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 2s
2026-04-30 10:43:39 +00:00
ab9a714b3e change: wait 10s before showing logs
Some checks failed
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Successful in 9s
Blog Deployment / Deploy-Staging (push) Successful in 10s
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 2s
2026-04-30 10:39:30 +00:00
2a1debc648 add: i18n pagination 2026-04-30 10:38:27 +00:00
11 changed files with 514 additions and 239 deletions

View File

@@ -108,7 +108,7 @@ jobs:
cd /blog
docker compose down ${CONTAINER_NAME}
docker compose up -d ${CONTAINER_NAME}
sleep 5
sleep 30
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
@@ -163,7 +163,7 @@ jobs:
cd /blog
docker compose down ${CONTAINER_NAME}
docker compose up -d ${CONTAINER_NAME}
sleep 5
sleep 30
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}
@@ -194,7 +194,10 @@ jobs:
steps:
- name: Remove Old Docker Image
run: |
docker image rm $(docker image ls ${DOCKER_IMAGE} 2> /dev/null | awk '$NF != "U" && NR>1 {print $2}')
IMAGE_IDS=$(docker image ls "${DOCKER_IMAGE}" 2>/dev/null | awk '$NF != "U" && NR>1 {print $2}')
if [ -n "$IMAGE_IDS" ]; then
docker image rm $IMAGE_IDS
fi
Notify:
needs: [Check-Rebuild, Build, Deploy-Staging, Test-Staging, Merge, Deploy-Production, Test-Production, Clean]

View File

@@ -109,7 +109,7 @@ jobs:
cd /blog
docker compose down ${CONTAINER_NAME}
BLOG_TEST_BRANCH=${{ gitea.ref_name }} docker compose up -d ${CONTAINER_NAME}
sleep 5
sleep 30
echo "- Displaying container logs"
docker compose logs ${CONTAINER_NAME}

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1,283 @@
---
slug: migrate-passive-opnsense-node-to-truenas
title: Migrer mon nœud OPNsense HA passif vers TrueNAS
description: Jai migré ma VM OPNsense HA passive de Proxmox vers TrueNAS pour garder le routage et le firewalling disponibles même lorsque mon cluster Proxmox est arrêté.
date: 2026-05-24
draft: false
tags:
- opnsense
- truenas
- proxmox
- high-availability
categories:
- homelab
---
## Intro
Mon réseau homelab est géré par un cluster OPNsense composé de deux nœuds VM. Ces deux VM fonctionnent dans mon cluster Proxmox VE. Vous pouvez trouver les détails dans cet [article]({{< ref "post/15-migration-opnsense-proxmox-highly-available" >}}).
Cette configuration fonctionne bien la plupart du temps. Le problème concerne plutôt les rares cas où le cluster Proxmox lui-même est arrêté. Quand cela arrive, les deux nœuds OPNsense sont indisponibles en même temps, ce qui signifie quil ne me reste aucun routeur, donc aucun réseau du tout.
Récemment, jai installé un serveur TrueNAS dans le lab, que j'ai documenté dans ce [post]({{< ref "post/18-create-nas-server-with-truenas" >}}). Il est principalement là pour agir comme NAS, mais il pourrait aussi héberger des machines virtuelles. Cela me donne une bonne opportunité daméliorer la résilience de mon réseau sans changer toute la conception.
💡 Lidée est simple : garder le nœud OPNsense actif sur Proxmox, mais déplacer le nœud passif vers TrueNAS.
De cette façon, si le cluster Proxmox tombe, le nœud OPNsense passif peut toujours prendre le relais et garder le réseau fonctionnel.
---
## Préparer les nœuds OPNsense
Avant de déplacer quoi que ce soit, je veux massurer que les VM OPNsense peuvent fonctionner avec moins de mémoire.
Le serveur TrueNAS na pas autant de RAM disponible que le cluster Proxmox, donc la première étape est de réduire lallocation mémoire des nœuds OPNsense au minimum.
Je commence avec le nœud passif, `cerbere-head2` :
- Éteindre le nœud passif
- Réduire son allocation mémoire de 4 à 2GB
- Le redémarrer
- Vérifier la santé du cluster
- Basculer le service vers le nœud passif
- Exécuter des vérifications réseau
Ensuite, je répète la même opération sur le nœud actif, `cerbere-head1`.
Le faire un nœud à la fois me permet de garder le cluster HA en bonne santé tout en validant que lallocation mémoire réduite est toujours suffisante pour ma configuration.
---
## Préparer le réseau TrueNAS
La partie la plus importante de cette migration nest pas lexport du disque ni la création de la VM. Cest le réseau.
Une VM OPNsense nest pas un simple serveur avec une seule interface de management. Elle a besoin daccéder à plusieurs réseaux, incluant le management, le WAN, les réseaux utilisateurs, lIoT, pfSync, la DMZ et les réseaux lab.
Du côté TrueNAS, je commence depuis `System` > `Network` et jajoute des interfaces VLAN.
La première est le VLAN utilisateur :
- Type : `VLAN`
- Nom : `vlan13`
- Description : `User`
- Interface parente : `enp1s0`
- Tag VLAN : `13`
![Créer linterface VLAN utilisateur dans TrueNAS](images/truenas-create-new-vlan-interface.png)
Jajoute ensuite les autres VLANs de la même manière.
TrueNAS napplique pas les changements réseau directement. Il donne loption de tester les changements dabord, avec une courte fenêtre de validation. Si la configuration nest pas confirmée à temps, il revient automatiquement en arrière.
Cest vraiment pratique lorsquon change la configuration réseau de la machine à laquelle on est actuellement connecté.
![Confirmer les interfaces VLAN avant dappliquer les changements réseau](images/truenas-network-confirm-add-vlans.png)
Pour le réseau de management, jai créé un bridge appelé `br1`.
Ce bridge porte la configuration IP de management de TrueNAS à la place de linterface physique `enp1s0`, parce quelle doit aussi être partagée avec la VM OPNsense.
![Créer le bridge de management pour TrueNAS et la VM OPNsense](images/truenas-network-mgmt-bridge.png)
Après cela, je retire la configuration IP de linterface physique et je la garde sur le bridge.
![Configuration réseau avant dappliquer les changements du bridge](images/truenas-network-changes-before-apply.png)
Jai initialement essayé dutiliser DHCP pour le bridge de management après avoir mis à jour ladresse MAC dans Dnsmasq, mais jai finalement décidé de garder une adresse IP statique pour TrueNAS. Après certains changements réseau, DHCP a donné une autre adresse du pool, donc ladressage statique était loption la plus sûre et la plus simple pour ce serveur.
Pour la VM OPNsense, je crée un bridge pour chaque VLAN. Par exemple, `br13` utilise `vlan13`, je déplace aussi la description, comme `User`, de linterface VLAN vers le bridge pour plus de clarté.
La configuration réseau finale de TrueNAS :
![Créer un bridge par VLAN pour la VM OPNsense](images/truenas-network-bridges-for-vlan.png)
---
## Créer un dataset dexport temporaire
Pour déplacer le disque de la VM OPNsense passive de Proxmox vers TrueNAS, jai dabord besoin dun endroit pour exporter limage disque.
Dans TrueNAS, je crée un dataset nommé `storage/vm/disk`, puis je crée un partage NFS à partir de celui-ci.
Dans les options avancées du partage NFS, jai configuré :
- Utilisateur Maproot : `root`
- Hôtes autorisés :
- `192.168.88.21`
- `192.168.88.22`
- `192.168.88.23`
Ce sont les nœuds Proxmox VE autorisés à monter le partage.
Je ne crée pas manuellement de zvol à ce moment-là. Le processus de création de VM dans TrueNAS gère limport et la conversion du disque.
---
## Exporter le disque de la VM depuis Proxmox
Depuis linterface web Proxmox VE, je localise le nœud qui héberge la VM OPNsense passive `cerbere-head2`, elle fonctionne sur `Zenith`.
Je me connecte à ce nœud Proxmox en SSH et je monte le partage NFS depuis TrueNAS :
```bash
mount granite.mgmt.vezpi.com:/mnt/storage/vm/disk /mnt
```
Ensuite, jéteins la VM depuis linterface Proxmox VE. Je ne léteins pas depuis lintérieur dOPNsense parce que la VM a la HA activée.
Une fois la VM arrêtée, jexporte le disque principal en qcow2. Je nexporte pas le disque EFI.
```bash
qemu-img convert -f raw -O qcow2 -p \
rbd:ceph-workload/vm-123-disk-1 \
/mnt/cerbere-head2.qcow2
```
La conversion a pris environ une minute pour un disque de 20 GB.
À ce stade, le disque OPNsense passif est disponible sur TrueNAS et prêt à être importé dans une nouvelle VM.
---
## Recréer la VM OPNsense dans TrueNAS
Létape suivante consiste à recréer la VM OPNsense passive dans TrueNAS avec des paramètres correspondant aussi étroitement que possible à la VM dorigine.
Depuis linterface web TrueNAS, je vais dans la section `Virtual Machines`.
![La section Machines virtuelles dans TrueNAS](images/truenas-vm-menu.png)
Je crée une nouvelle VM avec ces paramètres.
Pour le système dexploitation :
- Système dexploitation invité : `FreeBSD`
- Nom : `cerberehead2`
- Horloge système : `Local`
- Méthode de démarrage : `UEFI`
- Activer Secure Boot : désactivé
- Activer Trusted Platform Module : désactivé
- Timeout darrêt : `90`
- Démarrer au boot : activé
- Activer laffichage VNC : désactivé
Le nom de la VM nutilise pas de tirets parce que TrueNAS ne les autorise pas ici.
Pour le CPU et la mémoire :
- CPU virtuels : `1`
- Cœurs : `2`
- Threads : `1`
- Mode CPU : `Custom`
- Modèle CPU : `qemu64`
- Taille mémoire : `2 GiB`
Pour le disque :
- Créer une nouvelle image disque
- Importer une image : activé
- Source de limage : `/mnt/storage/vm/files/cerbere-head2.qcow2`
- Type de disque : `VirtIO`
- Emplacement de stockage : `storage/vm`
- Taille : `20 GiB`
Pour la première interface réseau :
- Type dadaptateur : `VirtIO`
- Adresse MAC : garder celle proposée
- Attacher la NIC : `br1: Mgmt`
Je passe le média dinstallation et la configuration GPU, puis je confirme le résumé.
![Résumé avant de créer la VM OPNsense dans TrueNAS](images/truenas-vm-create-new-summary.png)
Après confirmation, TrueNAS convertit limage qcow2 importée en zvol.
![TrueNAS convertissant limage disque importée en zvol](images/truenas-vm-disk-image-conversion.png)
Une fois la VM créée, jouvre les détails de la VM et jajoute les NICs restantes.
![Les périphériques de la VM dans TrueNAS](images/truenas-vm-details.png)
Pour chaque NIC supplémentaire, jai utilisé VirtIO comme type dadaptateur et je lai attachée au bridge correspondant.
Pour la NIC WAN, je copie lancienne adresse MAC parce que jutilise une astuce avec une seule adresse IP WAN. Jincrémente aussi le chiffre dans lordre des périphériques pour garder le même que dans Proxmox.
![Interface réseau VirtIO supplémentaire pour la VM OPNsense](images/truenas-vm-add-nic.png)
🎉 Enfin, je peux démarrer la VM OPNsense dans TrueNAS.
![OPNsense démarre avec succès comme VM TrueNAS](images/truenas-vm-opnsense-start-shell.png)
---
## Valider le cluster HA
Une fois que le nœud passif fonctionne sur TrueNAS, je dois valider que le cluster HA OPNsense se comporte toujours correctement.
Je commence par des vérifications de base sur le nœud passif :
- Ping de linterface de management depuis le bastion : `192.168.88.3`
- Ping de linterface utilisateur depuis un laptop : `192.168.13.3`
- Ping de linterface IoT : `192.168.37.3`
- Ping pfSync depuis lautre nœud : `192.168.44.2`
- Ping de linterface DMZ : `192.168.55.3`
- Ping de linterface Lab depuis DockerVM : `192.168.66.3`
Je vérifie aussi que le nœud était accessible en SSH depuis mon laptop en utilisant `192.168.13.3`, et que linterface web était joignable à :
```text
https://192.168.13.3:4443
```
Ensuite, je valide létat HA dOPNsense :
- Le statut des VIP CARP doit être `BACKUP` sur toutes les VIP
- La page de statut HA doit montrer que le nœud actif peut se connecter au nœud passif
- Les services doivent fonctionner comme attendu
- La synchronisation des services HA doit fonctionner
- Les vérifications de mise à jour du firmware doivent être accessibles
Depuis le nœud actif, jutilise la page de statut HA et je force une synchronisation complète avec `Synchronize and reconfigure all`.
---
## Tests de bascule contrôlée
Avant de tester le failover, je démarre une session SSH vers `dockerVM` pour confirmer que les états du firewall sont préservés entre les nœuds. Je démarre aussi un ping depuis un laptop vers `192.168.37.120`.
Pour le test de bascule contrôlée, jactive proprement le mode maintenance sur le nœud master.
Le nouveau nœud passif devient `MASTER`, et je valide les services importants :
- Routage VLAN supplémentaire avec un ping vers `192.168.37.120`
- Accès WAN avec un ping vers `8.8.8.8`
- États du firewall en gardant la session SSH active
- Résolution DNS externe avec `host redhat.com`
- Résolution DNS interne avec `host SLZB-06M.mgmt.vezpi.com`
- Accès à une page internet aléatoire
- Reverse proxy Caddy
- Proxy layer4 Caddy
- Accès Wireguard depuis lextérieur
- mDNS en vérifiant si limprimante est apparue
✅ La bascule contrôlée est réussie.
---
## Tests de failover
Après le test de bascule contrôlée propre, je teste un scénario de failover plus direct en forçant un poweroff du nœud actif.
Jai répété la même checklist de validation.
✅ Le failover est réussi.
Enfin, je redémarre la VM OPNsense active.
🎯 À ce stade, le cluster HA OPNsense est de nouveau opérationnel, avec le nœud passif qui fonctionne maintenant sur TrueNAS au lieu de Proxmox.
---
## Conclusion
Cette migration est une petite mais importante amélioration pour mon homelab.
Avant, les deux nœuds OPNsense dépendaient du cluster Proxmox VE. Si le cluster était arrêté, toute ma couche de routage réseau était arrêtée avec lui.
Maintenant, le nœud actif fonctionne toujours sur Proxmox, mais le nœud passif fonctionne sur TrueNAS. Cela me donne une meilleure séparation entre le cluster de virtualisation et la couche de failover réseau.
Petit disclaimer, bien que TrueNAS offre des fonctionnalités de virtualisation, il nest pas comparable à Proxmox VE en termes de clustering et de capacités de gestion dinfrastructure.
Une note à propos de QEMU Guest Agent, la VM OPNsense avait déjà QEMU Guest Agent installé avant lexport. Dans cette configuration, il ne semble pas utile parce que TrueNAS ne la pas implémenté comme fonctionnalité dhyperviseur. Je lai gardé installé quand même, parce quil est inoffensif.

View File

@@ -1,147 +1,127 @@
---
slug: migrate-passive-opnsense-node-to-truenas
title: Migrate my Passive OPNsense Node to TrueNAS
title: Migrate my Passive OPNsense HA Node to TrueNAS
description: I migrated my passive OPNsense HA VM from Proxmox to TrueNAS to keep routing and firewalling available even when my Proxmox cluster is down.
date: 2026-03-12
draft: true
date: 2026-05-24
draft: false
tags:
- opnsense
- truenas
- proxmox
- high-availability
categories:
- homelab
---
## Intro
My router is the heart of my homelab. When its down, everything is down: internet, DNS, VLAN firewall, reverse proxy… the whole stack.
My homelab network is handled by an OPNsense cluster composed of two VM nodes. Both of these VMs are running inside my Proxmox VE cluster. You can find details in this [article]({{< ref "post/15-migration-opnsense-proxmox-highly-available" >}}).
Im running an [[OPNsense]] HA cluster made of **two virtual machines** inside my [[Proxmox]] VE cluster. It works great… except for one annoying edge case: when the Proxmox cluster is down (rare, but it happens), I suddenly have **no router left**.
This setup works fine most of the time. The issue is more about the rare cases where the Proxmox cluster itself is down. When that happens, both OPNsense nodes are unavailable at the same time, which means I do not have any router left, so no network at all.
Recently I installed a [[TrueNAS]] server ([[Build my NAS with TrueNAS]]), and TrueNAS can host virtual machines. So I decided to move **only the passive OPNsense node** to TrueNAS, so that if Proxmox goes dark, I still have a node alive that can take over and keep the network running.
Recently, I installed a TrueNAS server in the labwhich I document in that [post]({{< ref "post/18-create-nas-server-with-truenas" >}}). It is mainly here to act as a NAS, but it could also host virtual machines. That give me a good opportunity to improve the resilience of my network without changing the whole design.
The objective of this post is simple: explain what I migrated, why I did it, and what configuration choices made it work reliably.
💡 The idea is simple: keep the active OPNsense node on Proxmox, but move the passive node to TrueNAS.
This way, if the Proxmox cluster goes down, the passive OPNsense node can still take over and keep the network alive.
---
## Prepare the OPNsense Nodes
## The Plan: Split the HA Pair Across Two Hypervisors
Before moving anything, I want to make sure the OPNsense VMs could run with less memory.
The goal was:
The TrueNAS server does not have as much RAM available as the Proxmox cluster, so the first step is to reduce the memory allocation of the OPNsense nodes to the minimum.
- Keep the **active** OPNsense node running on Proxmox VE (where it already lives).
- Migrate the **passive** node to TrueNAS.
- Validate that the HA cluster still behaves properly (CARP VIPs, sync, services, failover).
I start with the passive node, `cerbere-head2`:
This way, a Proxmox outage no longer means “no routing at all”.
- Shut down the passive node
- Reduce its memory allocation from 4 to 2GB
- Restart it
- Verify the cluster health
- Swap the service to the passive node
- Run network checks
Then I repeat the same operation on the active node, `cerbere-head1`.
Doing it one node at a time allow me to keep the HA cluster healthy while validating that the reduced memory allocation is still enough for my setup.
---
## Prepare the TrueNAS Network
## What I Used
The most important part of this migration is not the disk export or the VM creation. It is the network.
Quick overview of the pieces involved:
An OPNsense VM is not a simple server with one management interface. It needs access to several networks, including management, WAN, user networks, IoT, pfSync, DMZ and lab networks.
- **OPNsense**: https://opnsense.org/
- **Proxmox VE** (current home of both OPNsense VMs): https://www.proxmox.com/en/proxmox-virtual-environment/overview
- **TrueNAS** (new home of the passive node, and storage to transfer the VM disk): https://www.truenas.com/
On the TrueNAS side, I start from `System` > `Network` and add VLAN interfaces.
The first one is the User VLAN:
- Type: `VLAN`
- Name: `vlan13`
- Description: `User`
- Parent interface: `enp1s0`
- VLAN tag: `13`
![Create the User VLAN interface in TrueNAS](images/truenas-create-new-vlan-interface.png)
I then add the other VLANs in the same way.
TrueNAS does not apply network changes directly. It gives the option to test the changes first, with a short validation window. If the configuration is not confirmed in time, it rolls back automatically.
This is really convenient when changing the network configuration of the machine you are currently connected to.
![Confirm the VLAN interfaces before applying the network changes](images/truenas-network-confirm-add-vlans.png)
For the management network, I created a bridge called `br1`.
This bridge holds the TrueNAS management IP configuration instead of the physical interface `enp1s0`, because it also needs to be shared with the OPNsense VM.
![Create the management bridge for TrueNAS and the OPNsense VM](images/truenas-network-mgmt-bridge.png)
After that, I remove the IP configuration from the physical interface and keep it on the bridge.
![Network configuration before applying the bridge changes](images/truenas-network-changes-before-apply.png)
I initially tried to use DHCP for the management bridge after updating the MAC address in Dnsmasq, but I finally decided to keep a static IP address for TrueNAS. After some network changes, DHCP gave another address from the pool, so static addressing was the safer and simpler option for this server.
For the OPNsense VM, I create a bridge for each VLAN. For example, `br13` uses `vlan13`, I also move the description, like `User`, from the VLAN interface to the bridge for clarity.
The final TrueNAS network configuration:
![Create one bridge per VLAN for the OPNsense VM](images/truenas-network-bridges-for-vlan.png)
---
## Create a Temporary Export Dataset
## Step 1 — Make OPNsense Lighter (RAM Reduction)
To move the passive OPNsense VM disk from Proxmox to TrueNAS, I first need a place to export the disk image.
TrueNAS on my side doesnt have “infinite RAM”, so the first step was to reduce memory usage to something more reasonable.
In TrueNAS, I create a dataset named `storage/vm/disk`, then create a NFS share from it.
I reduced the memory allocation of both OPNsense nodes in Proxmox:
In the advanced options of the NFS share, I configured:
- Shutdown passive node `cerbere-head2`
- Reduce RAM, restart, verify HA
- Swap services to the passive temporarily and test networking
- Shutdown active node `cerbere-head1`
- Reduce RAM, restart, verify HA again
- Maproot user: `root`
- Authorized hosts:
- `192.168.88.21`
- `192.168.88.22`
- `192.168.88.23`
This kept the cluster healthy while ensuring the VM would fit comfortably on the NAS.
These are the Proxmox VE nodes allowed to mount the share.
(Details: [[Reduce the memory allocation of OPNsense nodes]])
I don't manually create a zvol at that point. The VM creation process in TrueNAS handle the disk import and conversion.
---
## Export the VM Disk from Proxmox
## Step 2 — Prepare Networking on TrueNAS (Trunk + VLAN Strategy)
From the Proxmox VE web interface, I locate the node hosting the passive OPNsense VM `cerbere-head2`, it is running on `Zenith`.
To host an OPNsense VM properly, TrueNAS must be able to present the right networks to the VM (Mgmt, VLANs, etc.). In my case, I needed a trunk configuration.
In TrueNAS, I went to `System` > `Network` and created VLAN interfaces (example with VLAN 13):
![truenas-create-new-vlan-interface.png](images/truenas-create-new-vlan-interface.png)
TrueNAS is nice here: changes arent applied blindly. You can **test** them and you get a rollback window, which is exactly what you want when youre touching the network config remotely:
![truenas-network-confirm-add-vlans.png](images/truenas-network-confirm-add-vlans.png)
### Management bridge
I created a bridge `br1` for the management interface, shared between:
- TrueNAS itself
- the future OPNsense VM
And moved the IP configuration to the bridge:
![truenas-network-mgmt-bridge.png](images/truenas-network-mgmt-bridge.png)
Final view before apply:
![truenas-network-changes-before-apply.png](images/truenas-network-changes-before-apply.png)
### Static IP vs DHCP (and why I stayed static)
I initially tried switching the management bridge to DHCP by updating the MAC address in OPNsense (Dnsmasq override):
![opnsense-update-dnsmasq-override-truenas-bridge.png](images/opnsense-update-dnsmasq-override-truenas-bridge.png)
Then I attempted to flip TrueNAS from static to DHCP:
![truenas-network-bridge-switch-static-to-dhcp.png](images/truenas-network-bridge-switch-static-to-dhcp.png)
But DHCP didnt behave as I expected: it kept receiving random IPs from the pool. I suspected existing leases played a role. I even tried manually editing leases and restarting the service, but after another change, it still ended up with a random address again.
In the end, I gave up and kept **a static IP** for TrueNAS. Its boring, but its predictable.
### The key decision: bridge VLANs (not just VLAN interfaces)
This became important later: I originally planned to attach VLAN interfaces directly to the OPNsense VM, but it didnt behave well.
So I created **one bridge per VLAN** (ex: `br13` with `vlan13` as the only member), and used those bridges for the VM NICs:
![truenas-network-bridges-for-vlan.png](images/truenas-network-bridges-for-vlan.png)
That ended up being the difference between “split-brain chaos” and “stable HA”.
(Full notes: [[Configure the trunk in TrueNAS]])
---
## Step 3 — Move the VM Disk From Proxmox to TrueNAS
To migrate the VM cleanly, I exported the Proxmox disk to TrueNAS.
### Create a dataset and export it via NFS
I created a dataset (initially called `disk`) and exported it with NFS, restricting access to my three Proxmox nodes (by IP):
- 192.168.88.21
- 192.168.88.22
- 192.168.88.23
(Notes: [[Create a new dataset in TrueNAS to export Proxmox VM disk]])
### Export the passive OPNsense disk
On the Proxmox node hosting the passive VM (`cerbere-head2`), I mounted the NFS share:
I log into that Proxmox node over SSH and mount the NFS share from TrueNAS:
```bash
mount granite.mgmt.vezpi.com:/mnt/storage/disk /mnt
mount granite.mgmt.vezpi.com:/mnt/storage/vm/disk /mnt
```
Then I shut down the VM from Proxmox (HA enabled, so I didnt do it from inside OPNsense), and converted/exported the main disk (not the EFI disk) from Ceph RBD to a qcow2 file:
Then I shut down the VM from the Proxmox VE interface. I don't shut it down from inside OPNsense because the VM has HA enabled.
Once the VM is stopped, I export the main disk to qcow2. I don't export the EFI disk.
```bash
qemu-img convert -f raw -O qcow2 -p \
@@ -149,150 +129,155 @@ qemu-img convert -f raw -O qcow2 -p \
/mnt/cerbere-head2.qcow2
```
The conversion took around a minute for a 20GB disk.
The conversion took about one minute for a 20 GB disk.
(Notes: [[Export the passive OPNsense VM disk from Proxmox]])
At this point, the passive OPNsense disk is available on TrueNAS and ready to be imported into a new VM.
### Dataset reorg (cleaner layout)
---
## Recreate the OPNsense VM in TrueNAS
I reorganized datasets on TrueNAS side to something more VM-oriented:
The next step is to recreate the passive OPNsense VM in TrueNAS with parameters matching the original VM as closely as possible.
- created `storage/vm`
- renamed `storage/disk` to `storage/vm/files`
From the TrueNAS web interface, I go to the `Virtual Machines` section.
Commands used:
![The Virtual Machines section in TrueNAS](images/truenas-vm-menu.png)
```bash
zfs list
sudo zfs create storage/vm
sudo zfs rename storage/disk storage/vm/files
I create a new VM with these settings.
For the operating system:
- Guest Operating System: `FreeBSD`
- Name: `cerberehead2`
- System Clock: `Local`
- Boot Method: `UEFI`
- Enable Secure Boot: disabled
- Enable Trusted Platform Module: disabled
- Shutdown Timeout: `90`
- Start on Boot: enabled
- Enable Display VNC: disabled
The VM name does not use dashes because TrueNAS do not allow them there.
For CPU and memory:
- Virtual CPUs: `1`
- Cores: `2`
- Threads: `1`
- CPU Mode: `Custom`
- CPU Model: `qemu64`
- Memory Size: `2 GiB`
For the disk:
- Create new disk image
- Import Image: enabled
- Image source: `/mnt/storage/vm/files/cerbere-head2.qcow2`
- Disk Type: `VirtIO`
- Storage Location: `storage/vm`
- Size: `20 GiB`
For the first network interface:
- Adapter Type: `VirtIO`
- MAC Address: keep the proposed one
- Attach NIC: `br1: Mgmt`
I skip installation media and GPU configuration, then confirm the summary.
![Summary before creating the OPNsense VM in TrueNAS](images/truenas-vm-create-new-summary.png)
After confirmation, TrueNAS convert the imported qcow2 image into a zvol.
![TrueNAS converting the imported disk image into a zvol](images/truenas-vm-disk-image-conversion.png)
Once the VM is created, I open the VM details and add the remaining NICs.
![The VM devices in TrueNAS](images/truenas-vm-details.png)
For each additional NIC, I used VirtIO as the adapter type and attach it to the corresponding bridge.
For the WAN NIC, I copy the old MAC address because I use a single WAN IP address trick. I also increment the digit in the Device Order to keep the same as in Proxmox.
![Additional VirtIO network interface to the OPNsense VM](images/truenas-vm-add-nic.png)
🎉 Finally I can start the OPNsense VM in TrueNAS.
![OPNsense booting successfully as a TrueNAS VM](images/truenas-vm-opnsense-start-shell.png)
---
## Validate the HA cluster
Once the passive node is running on TrueNAS, I need to validate that the OPNsense HA cluster is still behaving correctly.
I start with basic checks on the passive node:
- Management interface ping from the bastion: `192.168.88.3`
- User interface ping from a laptop: `192.168.13.3`
- IoT interface ping: `192.168.37.3`
- pfSync ping from the other node: `192.168.44.2`
- DMZ interface ping: `192.168.55.3`
- Lab interface ping from DockerVM: `192.168.66.3`
I also check that the node was accessible over SSH from my laptop using `192.168.13.3`, and that the web interface was reachable at:
```text
https://192.168.13.3:4443
```
(Notes: [[Reorganize the dataset in TrueNAS]])
Then I validate the OPNsense HA state:
- CARP VIP status must be `BACKUP` on all VIPs
- HA status page must show that the active node can log in to the passive node
- Services must be running as expected
- HA service synchronization must work
- Firmware update checks must be accessible
From the active node, I use the HA status page and force a full synchronization with `Synchronize and reconfigure all`.
---
## Switchover Tests
## Step 4 — Create the OPNsense VM on TrueNAS (Import Disk + Rebuild NICs)
Before testing failover, I start a SSH session to `dockerVM` to confirm that firewall states are preserved across nodes. I also start a ping from a laptop to `192.168.37.120`.
Now the fun part: recreating the VM on TrueNAS with the same “spirit” as the Proxmox VM.
For the switchover test, I gracefully enable maintenance mode on the master node.
From `Virtual Machines`:
The new passive node become `MASTER`, and I validate the important services:
![truenas-vm-menu.png](images/truenas-vm-menu.png)
### VM settings I used
I created a new VM with:
**Operating System**
- Guest: FreeBSD
- Name: `cerberehead2` (TrueNAS doesnt like dashes)
- Boot: UEFI
- Secure Boot: Disabled
- TPM: Disabled
- Start on Boot: Enabled
- VNC: Disabled
**CPU & Memory**
- Virtual CPUs: 1
- Cores: 2
- Threads: 1
- CPU Mode: Custom
- CPU Model: `qemu64`
- Memory: 2 GiB
**Disk**
- Import image enabled
- Source: `/mnt/storage/vm/files/cerbere-head2.qcow2`
- Disk Type: VirtIO
- Location: `storage/vm`
- Size: 20 GiB
**Network**
- Adapter: VirtIO
- Attached to `br1` (Mgmt)
- MAC: kept the generated one here
Summary screen:
![truenas-vm-create-new-summary.png](images/truenas-vm-create-new-summary.png)
After saving, TrueNAS converted the imported image into a Zvol:
![truenas-vm-disk-image-conversion.png](images/truenas-vm-disk-image-conversion.png)
### Adding the additional NICs
After the VM was created, I added the additional NICs in the VM device list:
![truenas-vm-details.png](images/truenas-vm-details.png)
At first, I attached VLAN interfaces directly and started the VM… and instantly broke my network (great success).
The VM itself booted fine though, and seeing OPNsense come up cleanly on TrueNAS was a good sign:
![truenas-vm-opnsense-start-shell.png](images/truenas-vm-opnsense-start-shell.png)
But HA-wise, it was a mess: split-brain symptoms, with the TrueNAS-hosted node thinking it was MASTER on almost everything except Mgmt.
The fix was the VLAN bridging approach mentioned earlier: once I switched the VM NICs to attach to **bridges (`br13`, `br20`, etc.) instead of VLAN interfaces**, the cluster came back to a healthy state.
Second try: stable. ✅
(Notes: [[Create the OPNsense VM in TrueNAS]])
---
## Step 5 — Validate HA: CARP, Sync, Services, Switchover and Failover
Once everything was in place, I validated the new setup with a proper checklist. I wanted to be sure the cluster worked exactly as before.
### Basic checks
- Ping each interface as relevant (Mgmt/User/IoT/pfSync/DMZ/Lab)
- SSH access
- Web UI access
- CARP VIP status must be `BACKUP` on the passive node
- HA status (active must be able to log into passive)
- Services state + “Synchronize and reconfigure all”
- Check updates availability (`System` > `Firmware` > `Check for updates`)
### Switchover test (graceful)
I started:
- a SSH session to DockerVM (to check state keeping)
- a ping to an IoT host from a laptop
Then tested:
- CARP role switch
- inter-VLAN routing
- WAN ping to `8.8.8.8`
- firewall state (SSH session stays alive)
- DNS resolution (external + internal)
- Caddy reverse proxy + layer4 proxy checks
- Extra VLAN routing with ping to `192.168.37.120`
- WAN access with ping to `8.8.8.8`
- Firewall states by keeping the SSH session alive
- External DNS resolution with `host redhat.com`
- Internal DNS resolution with `host SLZB-06M.mgmt.vezpi.com`
- Access to a random internet page
- Caddy reverse proxy
- Caddy layer4 proxy
- Wireguard access from outside
- mDNS discovery (printer visibility)
- mDNS by checking if the printer showed up
Switchover successful.
### Failover test (hard)
Then I forced power off of the active node and repeated the same functional tests.
✅ Failover successful.
At the end: restarted the active VM, and the HA pair returned to normal operation.
One note: QEMU Guest Agent doesnt bring value here because TrueNAS doesnt implement it as a hypervisor (I still left it installed since its harmless).
(Full checklist and validation steps: [[Validate the new OPNsense VM and cluster state]])
The switchover is successful.
---
## Failover Tests
After the graceful switchover test, I test a more direct failover scenario by forcing a poweroff of the active node.
I repeated the same validation checklist.
✅ The failover is successful.
Finally, I restart the active OPNsense VM.
🎯 At that point, the OPNsense HA cluster is operational again, with the passive node now running on TrueNAS instead of Proxmox.
---
## Conclusion
This project solved a real weakness in my homelab: my “highly available” router cluster was still depending on a single platform (Proxmox). By moving only the **passive OPNsense node** to **TrueNAS**, I now have a router that can survive a full Proxmox outage.
This migration is a small but important improvement for my homelab.
The biggest takeaway for me was networking on TrueNAS: attaching VLAN interfaces directly to the VM was not reliable in my setup, but bridging each VLAN (`br13`, `br20`, etc.) made the HA behavior stable and predictable.
Before, both OPNsense nodes depended on the Proxmox VE cluster. If the cluster was down, my whole network routing layer was down with it.
Next step is to monitor the cluster for a few days before doing the cleanup of the migration on the Proxmox side.
Now, the active node still runs on Proxmox, but the passive node runs on TrueNAS. This gives me a better separation between the virtualization cluster and the network failover layer.
Little disclaimer, while TrueNAS offers virtualization features, it is not comparable to Proxmox VE in terms of clustering and infrastructure management capabilities.
A note about QEMU Guest Agent, the OPNsense VM already had the QEMU Guest Agent installed before expert. In this setup, it does not seem useful because TrueNAS does not have it implemented as a hypervisor feature. I kept it installed anyway, because it is harmless.

View File

@@ -21,12 +21,6 @@ rm -rf "$CLONE_DIR"
echo "- Cloning $REPO_URL (branch: $BRANCH)..."
git clone --recurse-submodules --branch "$BRANCH" "$REPO_URL" "$CLONE_DIR"
# Patch references not yet fixed in Stack theme
sed -i 's/\.Site\.Data/hugo.Data/g' "$CLONE_DIR/themes/stack/layouts/_partials/article/components/photoswipe.html"
sed -i 's/LanguageDirection/Direction/g' "$CLONE_DIR/themes/stack/layouts/baseof.html"
sed -i 's/\.LanguageCode/.Language.Locale/g' "$CLONE_DIR/themes/stack/layouts/baseof.html"
sed -i 's/\.LanguageCode/.Locale/g' "$CLONE_DIR/themes/stack/layouts/rss.xml"
# Generate static files with hugo
echo "- Building site with Hugo v$HUGO_VERSION in $HUGO_DEST..."
hugo --source "$CLONE_DIR" --destination "$HUGO_DEST" --baseURL="https://${URL}" ${DRAFTS} --logLevel info --cleanDestinationDir --gc --panicOnWarning --printI18nWarnings

View File

@@ -76,3 +76,8 @@ footer:
designedBy:
other: " "
pagination:
jumpToPage: "Jump to page"
jump: "Go"
pressEnter: "Press Enter to jump"

View File

@@ -75,3 +75,8 @@ footer:
designedBy:
other: " "
pagination:
jumpToPage: "Aller à la page"
jump: "Aller"
pressEnter: "Presser Entrée pour aller"