← Zurück zum Blog

Incus Networking: Proxy-Device, NAT und der Weg nach draußen

Incus Networking: Proxy-Device, NAT und der Weg nach draußen

Nach dem Starten des ersten Containers steht man vor der Frage: Wie bekomme ich einen Dienst aus dem Container nach draußen? Incus bietet dafür mehrere Wege – der flexibelste ist das Proxy-Device. Dieser Beitrag erklärt, wie es funktioniert, und vor allem, was der Unterschied zwischen mit und ohne NAT ist.

Die Ausgangslage

Ein frischer Incus-Container läuft im managed Bridge-Netzwerk incusbr0 und bekommt eine private IP:

incus launch images:debian/12 webserver
incus list
+-----------+---------+------+-----------+
|   NAME    |  STATE  | IPV4            |
+-----------+---------+------+-----------+
| webserver | RUNNING | 10.68.100.2    |
+-----------+---------+------+-----------+

Der Container ist von außen nicht erreichbar. Die Bridge hat NAT – der Container kommt ins Internet, aber nichts kommt rein.

Proxy-Device – der Standardweg

Ein Proxy-Device leitet einen Port des Hosts an einen Dienst im Container weiter. Angelegt wird es pro Container:

incus config device add webserver webproxy proxy \
  listen=tcp:0.0.0.0:8080 \
  connect=tcp:127.0.0.1:80

Jetzt ist Port 8080 des Hosts auf Port 80 des Containers gemappt. curl http://:8080 erreicht den Dienst im Container.

Wie es funktioniert

Das Proxy-Device startet einen userspace-Prozess (incus-proxy), der im Namespace des Containers läuft. Er öffnet auf dem Host einen TCP-Socket, nimmt Verbindungen an und leitet sie an die connect-Adresse innerhalb des Containers weiter.

Client ─── Host:8080 ─── incus-proxy (im Container-NS) ─── Container:80

Weil der Proxy im Container-Namespace läuft, ist 127.0.0.1 der Container – nicht der Host. Das ist der entscheidende Punkt.

Vorteile:

  • Einfach, kein Kernel-Tuning, keine feste IP nötig
  • Funktioniert immer, auch ohne IP-Forwarding
  • connect=tcp:127.0.0.1:... ist die Container-eigene Loopback-Adresse

Nachteile:

  • Client-IP geht verloren (Container sieht Host/Bridge als Quelle)
  • Nur TCP und UDP, aber auch Unix-Sockets möglich

Proxy mit NAT – wenn die Client-IP gebraucht wird

Mit nat=true arbeitet das Proxy-Device anders:

incus config device add webserver webproxy proxy \
  listen=tcp:192.168.1.100:8080 \
  connect=tcp:10.68.100.2:80 \
  nat=true

Statt eines userspace-Prozesses legt Incus eine iptables-DNAT-Regel auf dem Host an. Der Kernel schreibt das Ziel der Pakete um und leitet sie direkt an den Container weiter.

Client ─── Host:8080 ─── DNAT (Kernel) ─── 10.68.100.2:80

Wichtig: Bei nat=true gelten besondere Adress-Regeln:

  • listen muss eine echte IP des Hosts sein (z. B. 192.168.1.100).
  • connect muss die tatsächliche IP des Containers sein (z. B.

0.0.0.0 oder Wildcards sind nicht erlaubt, weil die DNAT-Regel ein konkretes Ziel benötigt. 10.68.100.2). 127.0.0.1 funktioniert nicht, weil die DNAT-Regel im Host-Namespace arbeitet – dort ist 127.0.0.1 der Host selbst.

Vorteile:

  • Container sieht die Original-Client-IP (DNAT ändert nur das Ziel)
  • Kein Userspace-Prozess, geringfügig weniger Overhead

Nachteile:

  • Container braucht eine feste IP (sonst zeigt die DNAT-Regel ins Leere)
  • Erfordert net.ipv4.ip_forward=1
  • Nur TCP und UDP

Feste IP für den Container vergeben

Für nat=true muss die Container-IP stabil sein. Incus kann das über das Device-Profil:

incus config device add webserver eth0 nic \
  name=eth0 \
  network=incusbr0 \
  ipv4.address=10.68.100.100

Oder beim Launch:

incus launch images:debian/12 webserver
incus config device override webserver eth0 ipv4.address=10.68.100.100
incus restart webserver

Jetzt hat der Container 10.68.100.100 – und die DNAT-Regel bleibt gültig.

Der entscheidende Unterschied: Client-IP und connect-Adresse

Aspekt ohne NAT (nat=false) mit NAT (nat=true)
Client-IP im Container Host/Bridge-IP Original Client-IP
listen-Adresse 0.0.0.0 oder beliebig Host-IP (z. B. 192.168.1.100)
connect-Adresse Container-Perspektive (127.0.0.1 oder 0.0.0.0) Container-IP (z. B. 10.68.100.2)
Feste Container-IP nötig Nein Ja
ip_forward nötig Nein Ja
Namespace Läuft im Container-NS Läuft im Host-NS (Kernel)

Ohne NAT (Proxy-Mode): incus-proxy läuft im Container-Namespace. Er nimmt auf dem Host Verbindungen an und leitet sie innerhalb des Containers weiter. Der Container sieht die Host/Bridge-IP als Quelle, weil der Proxy eine neue TCP-Verbindung aufbaut. Die connect-Adresse ist aus Sicht des Containers – 127.0.0.1 ist dort der Container selbst.

Mit NAT (NAT-Mode): Der Kernel schreibt per DNAT nur das Ziel-Paket um, nicht die Quelle. Der Container sieht die Original-Client-IP. Weil die Regel im Host-Namespace lebt, gelten besondere Adress-Regeln: listen braucht eine echte Host-IP (kein 0.0.0.0), connect braucht die Container-IP (nicht 127.0.0.1).

Proxy Protocol – wenn die Client-IP im Proxy-Mode gebraucht wird

Wer den einfacheren Proxy-Mode (nat=false) nutzen will, aber trotzdem die echte Client-IP braucht, kann Proxy Protocol aktivieren:

incus config device add webserver webproxy proxy \
  listen=tcp:0.0.0.0:8080 \
  connect=tcp:127.0.0.1:80 \
  proxy_protocol=true

incus-proxy hängt dann vor jede Verbindung einen Header mit der Original-Client-IP. Die Anwendung im Container muss Proxy Protocol verstehen – Nginx, Caddy, Traefik, HAProxy können das, viele andere nicht.

Bei nat=true brauchst du Proxy Protocol nicht, weil die Client-IP ohnehin durchkommt.

Praxisbeispiele

1. Einfacher Webserver ohne NAT

incus launch images:debian/12 web
incus exec web -- apt update && apt install -y nginx
incus config device add web http proxy \
  listen=tcp:0.0.0.0:80 \
  connect=tcp:127.0.0.1:80

Einfach, keine feste IP nötig, Client-IP wird nicht gebraucht.

2. Webdienst mit NAT und Client-IP

# Host-IP ermitteln
ip addr show incusbr0 | grep inet

incus launch images:debian/12 app
incus exec app -- apt update && apt install -y nginx
incus config device override app eth0 ipv4.address=10.68.100.50
incus restart app
incus config device add app http proxy \
  listen=tcp:192.168.1.100:8080 \
  connect=tcp:10.68.100.50:80 \
  nat=true

Der Dienst bekommt die echte Client-IP – nützlich für Zugriffslogs, Fail2ban oder Rate-Limiting. Container und Host brauchen feste IPs.

3. nginx mit Proxy Protocol

incus launch images:debian/12 web
incus exec web -- apt update && apt install -y nginx

In /etc/nginx/nginx.conf:

server {
    listen 80 proxy_protocol;
    real_ip_header proxy_protocol;
    ...
}
incus config device add web http proxy \
  listen=tcp:0.0.0.0:80 \
  connect=tcp:127.0.0.1:80 \
  proxy_protocol=true

Container-IP egal, keine feste IP nötig, Client-IP kommt trotzdem an.

Persönliche Anekdote: Wie ich aus Versehen einen offenen Relay betrieb

Als ich meinen ersten Mailserver in einem Incus-Container aufgesetzt habe, war ich froh, dass das Proxy-Device so einfach funktioniert. `incus config device add ... proxy listen=tcp:0.0.0.0:25 connect=tcp:127.0.0.1:25` – fertig, Port 25 offen, Mails kommen an. Dachte ich.

Was ich zu dem Zeitpunkt nicht wusste: Der Proxy-Mode öffnet eine neue Verbindung aus dem Host-Namespace. Mein Postfix im Container sah alle Verbindungen von der Host-IP kommen. Und Postfix vertraut dem lokalen Netzwerk – warum auch nicht? In meiner main.cf stand `mynetworks = 127.0.0.0/8 10.68.100.0/24`. Die Host-IP der Bridge (10.68.100.1) war eingeschlossen.

Ergebnis: Jeder, der Port 25 an meiner Host-IP erreichte, konnte beliebig Mails über meinen Server verschicken. Ich betrieb einen offenen Relay – eine der klassischen Top-3-Misskonfigurationen im Mail-Bereich.

Die Quittung kam ein paar Tage später von meinem Provider: Eine automatische Nachricht, mein Server versende Spam. Erst war ich ungläubig – ich betreibe seit über 20 Jahren Mailserver, so etwas kann mir nicht passieren. Also Logs gecheckt. Und dann wurde mir schlecht: Hunderte Verbindungen von fremden IPs, alle von Postfix akzeptiert, weil sie von der Host-IP kamen. Ich hab sofort alles abgeschaltet, Postfix vom Netz genommen und erstmal mein ganzes Setup hinterfragt.

Die Lösung war lächerlich einfach: nat=true mit fester Container-IP setzen. Postfix sah plötzlich die echten Client-IPs, lehnte unbekannte Absender ab. Aber die Peinlichkeit sitzt bis heute tief – ich hätte mich vorher über die Unterschiede informieren sollen, statt einfach froh zu sein, dass der Port offen war.

Entscheidungsmatrix

Szenario Proxy ohne NAT Proxy mit NAT
Einsteiger, 1–2 Dienste ✅ –
Client-IP im Log benötigt nur mit Proxy-Protokoll ✅
Feste Container-IP vergeben vermeiden ✅ –
Keine konkrete Host-IP angeben wollen ✅ –
Einfachste Konfiguration ✅ –
Produktiv mit Zugriffslogs – ✅

Fazit

Das Proxy-Device ist der einfachste Weg, Dienste aus Incus-Containern zugänglich zu machen. Der Default (nat=false) reicht für die meisten Fälle: keine feste IP nötig, 127.0.0.1 als connect-Adresse ist der Container, Konfiguration minimal.

Wer die echte Client-IP im Container braucht, hat zwei Wege: nat=true mit fester Container-IP, oder Proxy Protocol im Proxy-Mode – dann ohne feste IP, aber die Anwendung muss es unterstützen.