<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Firewall - Matteo Cisilino</title>
	<atom:link href="https://www.cisilino.com/de/tag/firewall-de/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.cisilino.com/de/</link>
	<description>Guide su WordPress, server e sicurezza</description>
	<lastBuildDate>Thu, 29 Oct 2026 19:02:10 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>
	<item>
		<title>MySQL 5.7 in Docker, geschützt mit nftables</title>
		<link>https://www.cisilino.com/de/2026/10/17/mysql-5-7-docker-nftables-schutz/</link>
					<comments>https://www.cisilino.com/de/2026/10/17/mysql-5-7-docker-nftables-schutz/#respond</comments>
		
		<dc:creator><![CDATA[matteocisilino]]></dc:creator>
		<pubDate>Sat, 17 Oct 2026 08:10:45 +0000</pubDate>
				<category><![CDATA[Server und Datenbanken]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Firewall]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[nftables]]></category>
		<guid isPermaLink="false">https://www.cisilino.com/?p=275</guid>

					<description><![CDATA[<p>So betreiben Sie MySQL 5.7 in Docker auf einem modernen Server, ohne es dem Internet auszusetzen: docker-compose, geschlossener Port und nftables-Firewall.</p>
<p>The post <a href="https://www.cisilino.com/de/2026/10/17/mysql-5-7-docker-nftables-schutz/">MySQL 5.7 in Docker, geschützt mit nftables</a> first appeared on <a href="https://www.cisilino.com/de/">Matteo Cisilino</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Vorbemerkung: warum MySQL 5.7 in Docker</h2>
<p>Wir brauchten <strong>MySQL 5.7 in Docker</strong> auf einem Server, der sonst mit einer aktuellen Distribution läuft. Einige Altanwendungen sind nicht mit MySQL 8 kompatibel (geänderte Standard-Authentifizierung, strengere SQL-Modi, neue reservierte Wörter), und sie kurzfristig zu migrieren kam nicht in Frage.</p>
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" src="https://www.cisilino.com/wp-content/uploads/2026/10/mysql-5-7-docker-nftables-absichern.webp" alt="MySQL 5.7 in Docker: eine Datenbank in einem Kasten mit orangefarbenem gestricheltem Rand, davor ein Kreis und ein Balken" width="1200" height="630"/></figure>
<p>Eine so alte Version direkt auf dem Host zu installieren, bringt mehrere Probleme. Die offiziellen Repositories aktueller Distributionen bieten sie nicht mehr an. Pakete von Drittanbietern können mit dem Rest des Systems in Konflikt geraten, und ein OS-Update könnte sie zerstören.</p>
<p>Wir haben uns deshalb entschieden, sie <strong>in einem Docker-Container zu isolieren</strong>. Der Host bleibt sauber und aktuell, die Datenbankversion ist festgelegt und reproduzierbar. Daten und Konfiguration liegen in einem Ordner, der sich leicht sichern und verschieben lässt.</p>
<p>Dieser Artikel erzählt, wie wir sie eingerichtet haben und vor allem, auf welche Hindernisse wir unterwegs gestoßen sind. Es sind die unbeabsichtigte Freigabe des Ports 3306 und ein iptables-Fehler beim Neustart. Dazu kam das Schreiben einer Firewall mit <strong>nftables</strong>, die nur autorisierte IPs durchlässt.</p>
<blockquote class="wp-block-quote">
<p><strong>Hinweis:</strong> MySQL 5.7 hat im Oktober 2023 das Ende des Supports (EOL) erreicht und erhält keine Sicherheitspatches mehr. Es ist eine Übergangslösung: Das Ziel bleibt die Migration auf MySQL 8.x oder MariaDB. Gerade deshalb ist es umso wichtiger, es nicht offen zu lassen.</p>
</blockquote>
<hr class="wp-block-separator has-alpha-channel-opacity"/>
<h2 class="wp-block-heading">1. Ordnerstruktur</h2>
<pre class="wp-block-code"><code>/opt/mysql57/
├── docker-compose.yml
├── .env
├── conf.d/        # configurazioni personalizzate (my.cnf aggiuntivi)
├── data/          # dati del database (volume persistente)
└── logs/
</code></pre>
<p>Die Datei <code>.env</code> enthält die Zugangsdaten, damit sie nicht im Compose landen.</p>
<pre class="wp-block-code"><code>TZ=Europe/Rome
MYSQL_ROOT_PASSWORD=una_password_molto_lunga_e_casuale
MYSQL_DATABASE=nome_db
MYSQL_USER=nome_utente
MYSQL_PASSWORD=altra_password_robusta
</code></pre>
<p>Schützen Sie sie mit <code>chmod 600 .env</code>.</p>
<h2 class="wp-block-heading">2. Die docker-compose.yml</h2>
<pre class="wp-block-code"><code>services:
  mysql57:
    image: mysql:5.7
    container_name: mysql57
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "3306:3306"
    environment:
      TZ: "${TZ:-Europe/Rome}"
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
      MYSQL_DATABASE: "${MYSQL_DATABASE}"
      MYSQL_USER: "${MYSQL_USER}"
      MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
    command:
      - --default-authentication-plugin=mysql_native_password
    volumes:
      - ./data:/var/lib/mysql
      - ./conf.d:/etc/mysql/conf.d:ro
      - ./logs:/var/log/mysql
    healthcheck:
      test: &#91;"CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"]
      interval: 20s
      timeout: 5s
      retries: 10
      start_period: 45s
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
</code></pre>
<p>Einige Entscheidungen, die man beachten sollte.</p>
<ul class="wp-block-list">
<li><strong><code>restart: unless-stopped</code></strong>: Die Datenbank startet nach einem Neustart des Servers von selbst wieder.</li>
<li><strong>Volumes als Bind-Mount</strong>: Daten, Konfiguration und Logs liegen auf dem Host, Backups und Migrationen sind also einfache Ordnerkopien.</li>
<li><strong>Healthcheck</strong>: erlaubt anderen Diensten zu warten, bis MySQL wirklich bereit ist (<code>$$</code> sorgt dafür, dass die Variable im Container und nicht von Compose aufgelöst wird).</li>
<li><strong><code>ulimits nofile</code></strong>: vermeidet den Fehler „too many open files“ unter Last.</li>
</ul>
<h2 class="wp-block-heading">3. Erstes Problem: Der Port von MySQL 5.7 in Docker ist für die ganze Welt offen</h2>
<p>Die Zeile <code>- "3306:3306"</code> entspricht <code>0.0.0.0:3306:3306</code>: Docker veröffentlicht den Port auf <strong>allen Schnittstellen</strong> des Hosts, auch der öffentlichen.</p>
<p>Das Heimtückischste ist, dass Docker seine eigenen Regeln direkt in die Firewall des Kernels schreibt und diese vor denen von UFW oder firewalld ausgewertet werden. Eine aktive Firewall <strong>genügt nicht</strong>: Der Port bleibt erreichbar. Die Docker-Dokumentation beschreibt dieses Verhalten unter <a href="https://docs.docker.com/engine/network/packet-filtering-firewalls/" target="_blank" rel="noopener">Packet filtering and firewalls</a>.</p>
<p>Die möglichen Wege sind.</p>
<ol class="wp-block-list">
<li><strong>Nur localhost</strong>: <code>"127.0.0.1:3306:3306"</code>, wenn die Datenbank nur vom Host oder über einen SSH-Tunnel genutzt wird.</li>
<li><strong>Kein veröffentlichter Port</strong>: Sind die Clients andere Container, genügt ein gemeinsames Docker-Netzwerk.</li>
<li><strong>Bestimmte IP</strong>: nur auf einer Adresse eines privaten Netzes/VPN veröffentlichen.</li>
<li><strong>Öffentlicher Port mit IP-Filter</strong>: unser Fall, weil sich ein externer Client (eine phpMyAdmin-Instanz auf einem anderen Server) verbinden können muss.</li>
</ol>
<p>Für Fall 4 braucht man eine Firewall, die den Verkehr der von Docker veröffentlichten Ports korrekt filtert.</p>
<h2 class="wp-block-heading">4. Zweites Problem: iptables-Fehler beim Start</h2>
<p>Nachdem wir die Firewall angefasst hatten, lieferte <code>docker compose up</code>.</p>
<pre class="wp-block-code"><code>failed to create network mysql57_default: Error response from daemon:
Failed to Setup IP tables: Unable to enable ACCEPT OUTGOING rule:
(iptables failed: iptables --wait -t filter -A DOCKER-FORWARD -i br-... -j ACCEPT:
iptables: No chain/target/match by that name.
</code></pre>
<p>Der Fehler hängt nicht vom Compose ab: Die Kette <code>DOCKER-FORWARD</code> (in neueren Docker-Versionen eingeführt) existierte nicht mehr. Sie war von etwas gelöscht worden, das die Regeln <strong>nach</strong> dem Start des Daemons bereinigt hatte. Ein Neustart von Docker (<code>systemctl restart docker</code>) legt sie neu an, doch das Problem kehrt zurück, bis der wahre Schuldige beseitigt ist.</p>
<h3 class="wp-block-heading">Der Schuldige: <code>flush ruleset</code></h3>
<p>Unsere <code>/etc/nftables.conf</code> begann mit.</p>
<pre class="wp-block-code"><code>flush ruleset
</code></pre>
<p>Dieser Befehl löscht <strong>alle</strong> nftables-Tabellen, auch die von Docker angelegten. Außerdem enthielt die Datei eine statische Kopie der Docker-Regeln, mit von Hand eingetragenem Bridge-Namen (<code>br-278550235c3f</code>) und Container-IP (<code>172.18.0.2</code>). Der Bridge-Name ändert sich jedoch jedes Mal, wenn das Compose-Netzwerk neu erstellt wird. Die Kopie war also sofort veraltet, und die Tabelle <code>ip nat</code> geriet in Konflikt mit der von Docker verwalteten.</p>
<p><strong>Goldene Regel:</strong> Mit Docker verwendet man in der nftables-Datei kein <code>flush ruleset</code> und kopiert keine seiner Regeln. Man verwaltet nur seine eigene Firewall, in eigenen Tabellen.</p>
<h2 class="wp-block-heading">5. Die Firewall mit nftables für MySQL 5.7 in Docker</h2>
<p>Das Prinzip: Die von Docker veröffentlichten Ports werden mit DNAT umgeschrieben und dann vom Host an den Container weitergeleitet, durchlaufen also die Kette <strong><code>forward</code></strong>, nicht <code>input</code>. Eine Regel <code>drop</code> in einer eigenen Kette <code>forward</code> mit etwas niedrigerer Priorität (also zuerst ausgewertet) verwirft das Paket unabhängig von den <code>accept</code>-Regeln von Docker.</p>
<p>Um fremde Tabellen nicht anzufassen, verwenden wir das Muster <code>table ...</code> + <code>delete table ...</code>: Es macht die Datei ohne Fehler und ohne <code>flush</code> wiederholt ausführbar.</p>
<p><code>/etc/nftables.conf</code>.</p>
<pre class="wp-block-code"><code>#!/usr/sbin/nft -f

# NIENTE "flush ruleset": cancellerebbe anche le tabelle di Docker

# ---------------- firewall host ----------------
table inet filter
delete table inet filter

table inet filter {

    chain input {
        type filter hook input priority filter; policy drop;
        ct state established,related accept
        iifname "lo" accept
        ip6 nexthdr icmpv6 accept
        iifname "enp7s0" accept                       # rete privata
        ip saddr 203.0.113.20 tcp dport 22 accept     # SSH da host fidato
        ip saddr 203.0.113.30 tcp dport 22 accept     # SSH da VPN
        ip saddr 203.0.113.20 icmp type echo-request accept
        tcp dport { 80, 443 } accept
    }

    chain output {
        type filter hook output priority filter; policy accept;
    }
}

# ---------------- guardia MySQL (porta pubblicata da Docker) ----------------
table ip mysql_guard
delete table ip mysql_guard

table ip mysql_guard {
    set allowed {
        type ipv4_addr
        flags interval
        elements = {
            203.0.113.10        # phpMyAdmin
        }
    }

    chain forward {
        type filter hook forward priority filter - 5; policy accept;
        iifname "enp7s0" accept
        ct status dnat tcp dport 3306 ip saddr @allowed accept
        ct status dnat tcp dport 3306 counter drop
    }
}
</code></pre>
<p>Wie die Schutzregel funktioniert.</p>
<ul class="wp-block-list">
<li><code>ct status dnat</code> wählt nur Pakete aus, die durch DNAT gelaufen sind, also die für die von Docker veröffentlichten Ports bestimmten.</li>
<li>Die erste Regel akzeptiert, wer im Set <code>allowed</code> ist; die zweite verwirft alle anderen und zählt sie (<code>counter</code>).</li>
<li>Lokaler Verkehr (<code>127.0.0.1</code>) und Verkehr zwischen Containern im selben Netzwerk läuft nicht über das Forward des Routers und wird daher nicht berührt.</li>
</ul>
<blockquote class="wp-block-quote">
<p><strong>Kompatibilität:</strong> In einer ersten Version verwendeten wir <code>ct original proto-dst 3306</code>, aber einige Versionen von <code>nft</code> antworten mit <em>„Can&#8217;t parse symbolic invalid expressions“</em>. Im Hook <code>forward</code> ist das DNAT bereits erfolgt und der Container-Port entspricht dem veröffentlichten (<code>3306:3306</code>). Daher ist <code>tcp dport 3306</code> zusammen mit <code>ct status dnat</code> gleichwertig und funktioniert überall. Ändern Sie das Mapping (z. B. <code>3307:3306</code>), muss die Regel an den internen Port angepasst werden.</p>
</blockquote>
<h3 class="wp-block-heading">Änderungen anwenden, ohne sich auszusperren</h3>
<p>Halten Sie eine zweite SSH-Sitzung als Reserve offen, dann.</p>
<pre class="wp-block-code"><code>nft -c -f /etc/nftables.conf     # solo verifica della sintassi
nft delete table ip nat          # una tantum: elimina la vecchia copia statica
nft -f /etc/nftables.conf
systemctl restart docker         # Docker ricrea le sue tabelle pulite
cd /opt/mysql57 &amp;&amp; docker compose up -d
</code></pre>
<p>Damit es beim Booten geladen wird, stellen Sie sicher, dass der Dienst aktiv ist (<code>systemctl enable --now nftables</code>) und dass <code>/etc/nftables.conf</code> kein <code>flush ruleset</code> enthält.</p>
<h3 class="wp-block-heading">IPs zur Laufzeit verwalten</h3>
<pre class="wp-block-code"><code>nft add element ip mysql_guard allowed { 203.0.113.50 }
nft delete element ip mysql_guard allowed { 203.0.113.50 }
</code></pre>
<p>Um es dauerhaft zu machen, aktualisieren Sie auch das Set in der Datei.</p>
<h2 class="wp-block-heading">6. Überprüfung</h2>
<p>Vom autorisierten Server.</p>
<pre class="wp-block-code"><code>nc -zv IP_PUBBLICO_DB 3306        # deve rispondere "succeeded"
</code></pre>
<p>Von jeder anderen IP.</p>
<pre class="wp-block-code"><code>nmap -Pn -p 3306 IP_PUBBLICO_DB   # deve risultare "filtered" / timeout
</code></pre>
<p>Auf dem Server muss der Zähler der Drop-Regel bei jedem nicht autorisierten Versuch wachsen.</p>
<pre class="wp-block-code"><code>nft list chain ip mysql_guard forward
ss -tlnp | grep 3306
</code></pre>
<h2 class="wp-block-heading">7. Zweite Barriere: die MySQL-Benutzer</h2>
<p>Die Firewall ersetzt nicht die Berechtigungen der Datenbank. Legen Sie den Benutzer mit auf die IP des Clients beschränktem Host an.</p>
<pre class="wp-block-code"><code>CREATE USER 'pma_user'@'203.0.113.10' IDENTIFIED BY 'password_robusta';
GRANT ALL ON nome_db.* TO 'pma_user'@'203.0.113.10';
FLUSH PRIVILEGES;
</code></pre>
<p>Gelingt die TCP-Verbindung, MySQL antwortet aber <code>Access denied</code>, liegt das Problem hier und nicht in der Firewall.</p>
<h2 class="wp-block-heading">8. Was man sich zu MySQL 5.7 in Docker merken sollte</h2>
<ul class="wp-block-list">
<li><strong>Docker und die Host-Firewall ignorieren sich nicht</strong>: Zu verstehen, welche Kette der Verkehr durchläuft (<code>input</code> oder <code>forward</code>), ist der Schlüssel zu allem.</li>
<li><strong>Niemals <code>flush ruleset</code></strong> auf einem Host mit Docker; verwenden Sie eigene Tabellen.</li>
<li><strong>Kopieren Sie keine von Docker erzeugten Regeln</strong> in Ihre Dateien: Bridge-Namen und IPs ändern sich.</li>
<li>Haben Sie den Port schon freigegeben, prüfen Sie die Logs in <code>./logs</code> und erwägen Sie, die <strong>Passwörter zu ändern</strong>, vor allem das von root.</li>
<li>Wo möglich, ziehen Sie ein <strong>VPN (WireGuard) oder einen SSH-Tunnel</strong> der öffentlichen Freigabe einer Datenbank vor.</li>
<li>Planen Sie die <strong>Migration</strong> von MySQL 5.7 auf eine unterstützte Version.</li>
</ul>
<p>Haben Sie Fragen oder eine andere Lösung für dasselbe Problem? Schreiben Sie es in die Kommentare. Zum Thema MySQL und Debian lesen Sie auch: <a href="https://www.cisilino.com/de/2026/10/13/mysql-startet-nicht-debian-12/">MySQL startet nach dem Debian-12-Upgrade nicht mehr</a>.</p><p>The post <a href="https://www.cisilino.com/de/2026/10/17/mysql-5-7-docker-nftables-schutz/">MySQL 5.7 in Docker, geschützt mit nftables</a> first appeared on <a href="https://www.cisilino.com/de/">Matteo Cisilino</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.cisilino.com/de/2026/10/17/mysql-5-7-docker-nftables-schutz/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
