← 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.