MariaDB und MySQL absichern: die Schritte nach der Installation

Veröffentlicht am 15 Min. Lesezeit

Nach dem Paketinstall ist eine Datenbank noch nicht sicher. Dieser Leitfaden geht mysql_secure_installation durch, klärt die unix_socket-Frage und zeigt eigene Benutzer je Anwendung.

Eine frisch installierte Datenbank auf einem Rootserver ist selten so unsicher, wie ältere Anleitungen behaupten, und selten so sicher, wie das Paketsystem suggeriert. Zwischen diesen beiden Polen liegen genau die Handgriffe, die dieser Artikel beschreibt: was mysql_secure_installation tatsächlich erledigt, warum die unix_socket-Frage auf modernen Systemen anders zu beantworten ist als 2015, wie ein Anwendungsbenutzer mit minimalen Rechten aussieht und wie man Passwörter aus der Prozessliste heraushält.

Alle Befehle laufen als root. Wer mit einem normalen Benutzer arbeitet, stellt sudo voran. Die Ausgaben stammen von Debian 13 und Debian 12 sowie Ubuntu 24.04 und Ubuntu 22.04.

Ausgangslage: welches Paket auf welcher Distribution läuft

Der erste Stolperstein liegt vor dem ersten Sicherheitsschritt. Debian liefert seit Jahren kein eigenes Paket namens mysql-server mehr. Der Name existiert dort nur noch als virtuelles Paket ohne eigene Version, sichtbar allein über die Abhängigkeiten von mariadb-server:

apt-cache policy mysql-server
mysql-server:
  Installed: (none)
  Candidate: (none)
  Version table:

Dieser Versionsvergleich liefert also nur unter Ubuntu ein Ergebnis, dort 8.0.46 sowohl auf 24.04 als auch auf 22.04. Auf Debian nimmt man stattdessen apt-cache showpkg mysql-server, das den virtuellen Charakter des Namens sichtbar macht.

Auf Debian ist die Datenbank damit immer MariaDB. Auf Ubuntu gibt es beide, und man muss sich entscheiden. Die Fassungen der aktuellen Distributionen:

Distributionmariadb-servermysql-server
Debian 1311.8nur virtueller Name, keine Version
Debian 1210.11nur virtueller Name, keine Version
Ubuntu 24.0410.118.0.46
Ubuntu 22.0410.68.0.46

Installiert wird wie gewohnt:

apt update
apt install -y mariadb-server

Ob man wirklich die erwartete Fassung erwischt hat, klärt nicht die Paketversion allein, sondern der laufende Server:

mariadb -e "SELECT @@version, @@version_comment;"

Der zweite Stolperstein ist der Programmname. Ab MariaDB 11.0 liegen die MySQL-kompatiblen Namen nicht mehr im Hauptpaket, sondern in mariadb-client-compat und mariadb-server-compat. Auf Debian 13 kann ein mysql --version deshalb entweder mit einer Warnung antworten oder gar nicht existieren:

mysql: Deprecated program name. It will be removed in a future release, use '/usr/bin/mariadb' instead
bash: mysql: command not found

Wer Skripte schreibt, die auf allen vier Systemen laufen sollen, nutzt unter MariaDB durchgängig mariadb, mariadb-dump und mariadb-secure-installation. Diese Namen existieren seit MariaDB 10.5, also auch auf Ubuntu 22.04.

mysql_secure_installation Schritt für Schritt

Das Werkzeug heißt je nach Server anders. Unter MariaDB ist der kanonische Name mariadb-secure-installation, unter MySQL 8.0 bleibt es mysql_secure_installation. Den alten Namen gibt es unter MariaDB auf Debian 12 sowie auf Ubuntu 24.04 und 22.04 weiterhin als Symlink, auf Debian 13 dagegen nicht mehr: dort existiert ausschließlich mariadb-secure-installation.

Ein Blick auf die Optionen lohnt sich, denn das Programm akzeptiert unter anderem --defaults-file, --socket und --protocol. Diese Übersicht steht allerdings nur im Handbuch:

man mariadb-secure-installation

Ein --help gibt es nämlich nicht. Das Skript wertet Argumente überhaupt nicht aus, es verschluckt den unbekannten Schalter stillschweigend und startet sofort den interaktiven Ablauf. Fehlt dabei eine echte Konsole, etwa in einer Pipeline oder in einem Container ohne Terminal, wiederholt es die Rückfrage nach dem Passwort endlos und kehrt nie von selbst zurück. Aufgerufen wird deshalb ohne Argument und in einer interaktiven Shell:

mariadb-secure-installation

Auf Debian 13 stellt das Skript dem Ablauf einen deutlichen Hinweis voran:

NOTE: MariaDB is secure by default in Debian. Running this script is useless at best,
and misleading at worst. This script will be removed in a future MariaDB release in Debian.

Debian hält den Durchlauf dort also für überflüssig und wird das Skript in einer künftigen Fassung entfernen, nachzulesen in /usr/share/doc/mariadb-server/README.Debian.gz. Die folgenden Schritte lohnen trotzdem, denn sie zeigen, was geprüft wird und warum auf einem aktuellen System kaum noch etwas zu ändern ist.

Der Ablauf beginnt mit der Frage nach dem Passwort für root. Der Wortlaut hängt von der Fassung des Skripts ab:

Enter current password for root (enter for none):

Neuere Fassungen fragen stattdessen:

Enter root user password or leave blank:

Gemeint ist beide Male dasselbe. Auf einer frischen Installation gibt es kein Passwort, hier drückt man einfach Enter.

Danach folgen die eigentlichen Entscheidungen. Die Reihenfolge und der Wortlaut unterscheiden sich zwischen MariaDB und MySQL, inhaltlich geht es um dieselben fünf Punkte.

Die unix_socket-Frage

Bei MariaDB erscheint als nächstes:

Switch to unix_socket authentication [Y/n]

Diese Frage verwirrt, weil sie auf allen hier behandelten Systemen bereits beantwortet ist. Seit MariaDB 10.4 ist root@localhost von Haus aus über das Plugin unix_socket abgesichert, und das gilt für 10.6 auf Ubuntu 22.04 genauso wie für 11.8 auf Debian 13. Bei MySQL 8.0 heißt das Gegenstück auth_socket, und der Assistent sagt das auch offen:

Skipping password set for root as authentication with auth_socket is used by default.

Praktisch bedeutet das: Wer als Systembenutzer root angemeldet ist, kommt mit mariadb ohne Passwort in die Datenbank. Wer es nicht ist, kommt gar nicht hinein, auch nicht mit dem richtigen Passwort. Das ist kein Mangel, sondern die stärkere Variante. Es gibt kein Passwort, das aus einem Backup, einer Konfigurationsdatei oder einem Screenshot abfließen kann. Die Antwort auf die Frage lautet also Ja beibehalten, und ein zusätzliches root-Passwort ist auf einem einzelnen Anwendungsserver überflüssig.

Prüfen lässt sich der Zustand nur an der echten Tabelle. Die naheliegende Abfrage SELECT user, host, plugin FROM mysql.user führt hier nämlich in die Irre: sie zeigt bei root den Wert mysql_native_password, obwohl in Wirklichkeit unix_socket greift. Bei MariaDB ab 10.4 ist mysql.user nur noch eine Sicht auf mysql.global_priv, und diese Sicht kennt pro Konto nur eine einzige Authentifizierungsmethode. Wer sich darauf verlässt, hält den Server fälschlich für passwortauthentifiziert. Die vollständige Regel steht im JSON-Feld Priv der echten Tabelle:

mariadb -e "SELECT User, Host, JSON_DETAILED(Priv) FROM mysql.global_priv;"

Für root auf localhost steht dort unter MariaDB sinngemäß:

{"plugin":"mysql_native_password","authentication_string":"invalid","auth_or":[{},{"plugin":"unix_socket"}]}

Der erste Eintrag ist die Passwortmethode gegen den unbrauchbaren Hash der Zeichenkette invalid, den niemand treffen kann. Der zweite Eintrag unter auth_or ist die tatsächlich greifende Socketauthentifizierung. Kompakter fragt man beides so ab:

mariadb -e "SELECT User, Host, JSON_VALUE(Priv,'$.plugin') AS plugin, JSON_QUERY(Priv,'$.auth_or') AS auth_or FROM mysql.global_priv;"

Erwartet wird bei root und localhost ein unix_socket, wenn nicht in der Spalte plugin, dann unter auth_or. Steht dort ausschließlich eine Passwortmethode und nichts unter auth_or, arbeitet das Konto rein mit Passwort. Unter MySQL 8.0 gibt es mysql.global_priv nicht, dort ist mysql.user eine echte Tabelle und die Spalte plugin muss auth_socket zeigen. Aus der MariaDB-Sicht lesen kann man übrigens weiterhin, hineinschreiben nicht mehr.

Nur wenn ein Werkzeug zwingend ein Passwort braucht, etwa ein Monitoring, das nicht als root läuft, stellt man um. Sinnvoller ist es dann aber, einen zweiten Administrationsbenutzer anzulegen, statt root umzuwidmen. In MariaDB ab 11.6, also auch in 11.8 auf Debian 13, lässt sich ein Konto zusätzlich an einen bestimmten Systembenutzer binden:

CREATE USER 'dbadmin'@'localhost' IDENTIFIED VIA unix_socket AS 'deploy';

Damit darf sich der Systembenutzer deploy als Datenbankbenutzer dbadmin anmelden, ohne dass die Namen übereinstimmen müssen. Auf Debian 12 und den Ubuntu-Fassungen wird die Zeichenkette nach AS noch ignoriert, dort müssen Systembenutzer und Datenbankbenutzer gleich heißen.

Die vier verbleibenden Fragen

Der Rest ist unstrittig und wird durchgehend mit Ja beantwortet: anonyme Benutzer entfernen, root-Anmeldung aus der Ferne verbieten, die Datenbank test samt Rechten entfernen, Rechtetabellen neu laden. Auf einem aktuellen Debian oder Ubuntu sind anonyme Benutzer und die Testdatenbank meist ohnehin nicht angelegt, das Skript meldet dann schlicht, dass nichts zu tun war.

Bei MySQL 8.0 kommt eine Frage hinzu, die MariaDB nicht kennt:

Would you like to setup VALIDATE PASSWORD component?

Diese Komponente erzwingt Mindestanforderungen an alle künftig gesetzten Passwörter. Sie ist nützlich, wenn mehrere Personen Benutzer anlegen. Sie ist lästig, wenn ein Provisionierungsskript zufällige Passwörter erzeugt, die aus Versehen kein Sonderzeichen enthalten. Dann bricht das Skript ab mit:

ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

Wer sie aktiviert, sollte den Passwortgenerator vorher darauf einstellen. Die Standardstufe ist MEDIUM und verlangt mindestens acht Zeichen, Groß- und Kleinbuchstaben, Ziffer und Sonderzeichen.

Wenn es schiefgeht: zurück in die Datenbank

Der häufigste Weg, sich auszusperren, ist der gut gemeinte Wechsel von unix_socket auf ein Passwort, das dann verloren geht. Oder umgekehrt: Ein altes Skript legt /etc/mysql/debian.cnf neu an, und plötzlich passt nichts mehr zusammen. Die Fehlermeldungen, nach denen dann gesucht wird, lauten:

ERROR 1698 (28000): Access denied for user 'root'@'localhost'
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded

Fehler 1698 heißt: Das Konto erwartet unix_socket, du bist aber nicht der passende Systembenutzer. Ein sudo davor löst das oft schon. Fehler 1045 heißt: Es wird ein Passwort erwartet und deines stimmt nicht.

Kommt man gar nicht mehr hinein, startet man den Server ohne Rechteprüfung. Bei MariaDB geht das sauber über die Umgebungsvariable, die die mitgelieferte systemd-Unit auswertet, ganz ohne Bearbeitung von Paketdateien:

systemctl stop mariadb
systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"
systemctl start mariadb

Jetzt verbindet man sich mit mariadb -u root und setzt den Zustand zurück. Wichtig ist das FLUSH PRIVILEGES vorweg, denn ohne geladene Rechtetabellen scheitert ALTER USER:

FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket;

Danach unbedingt aufräumen, sonst startet der Server nach jedem Reboot ungeschützt:

systemctl stop mariadb
systemctl unset-environment MYSQLD_OPTS
systemctl start mariadb

Bei MySQL 8.0 auf Ubuntu funktioniert dieser Weg nicht, weil die Unit keine solche Variable auswertet und weil ALTER USER unter --skip-grant-tables zusätzliche Fallstricke hat. Dort nimmt man eine Startdatei. Sie muss in einem Verzeichnis liegen, das AppArmor dem Serverprozess erlaubt, andernfalls scheitert der Start mit Can't open file. /var/lib/mysql-files ist erlaubt, /tmp nicht:

systemctl stop mysql
echo "ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;" > /var/lib/mysql-files/reset.sql
chown mysql:mysql /var/lib/mysql-files/reset.sql
systemctl edit mysql

In den Editor kommt eine Überschreibung des Startbefehls, die leere erste Zeile ist notwendig, um den Originalwert zu löschen:

[Service]
ExecStart=
ExecStart=/usr/sbin/mysqld --init-file=/var/lib/mysql-files/reset.sql

Nach systemctl daemon-reload und einem Start ist das Konto zurückgesetzt. Anschließend die Überschreibung mit systemctl revert mysql entfernen und die Datei löschen. Wer Datenbanken auf getrennten Systemen betreibt, findet ergänzende Hinweise zur Absicherung des Zugangs im Beitrag SSH-Server absichern.

Ein eigener Benutzer je Anwendung statt root

Der wirksamste Schritt kommt in keinem Assistenten vor. Anwendungen dürfen sich nicht als root verbinden, und sie brauchen auch kein GRANT ALL. Eine typische Webanwendung liest und schreibt Zeilen, sie legt keine Datenbanken an und sie liest keine Dateien vom Server.

CREATE DATABASE shopdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'shopapp'@'localhost' IDENTIFIED BY 'HierEinLangesZufallspasswort';
GRANT SELECT, INSERT, UPDATE, DELETE ON shopdb.* TO 'shopapp'@'localhost';

Drei Dinge sind daran wichtig. Erstens der Punkt in shopdb.* statt *.*: Rechte auf *.* sind globale Rechte und gelten auch für mysql und information_schema. Zweitens die Angabe @'localhost' statt @'%': Damit ist das Konto ausschließlich lokal nutzbar, selbst wenn der Port später einmal offen steht. Drittens fehlt WITH GRANT OPTION, denn ein Konto, das Rechte weitergeben kann, ist faktisch ein Administrator.

Schemaänderungen laufen dann über ein zweites Konto, das nur beim Deployment verwendet wird:

CREATE USER 'shopmigrate'@'localhost' IDENTIFIED BY 'EinAnderesLangesZufallspasswort';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX, REFERENCES ON shopdb.* TO 'shopmigrate'@'localhost';

Das klingt nach Mehraufwand und ist genau der Punkt, an dem eine SQL-Injection in der Anwendung zum ärgerlichen Datenleck statt zum Totalverlust wird. Ein Konto ohne DROP kann keine Tabelle löschen.

Kontrolliert wird nicht am Wortlaut des GRANT, sondern am Ergebnis:

SHOW GRANTS FOR 'shopapp'@'localhost';

Erwartet wird genau zwei Zeilen: ein GRANT USAGE ON *.*, das lediglich das Anmelderecht darstellt und keinerlei Datenzugriff bedeutet, und die Zeile mit den vier Rechten auf shopdb.*. Taucht dort ALL PRIVILEGES ON *.* auf, war der Punkt an der falschen Stelle. Die zweite, aussagekräftigere Probe ist eine Anmeldung mit dem neuen Konto und ein SHOW DATABASES;. Sichtbar sein dürfen nur information_schema und shopdb.

Ein häufiger Fehler beim Anlegen:

ERROR 1396 (HY000): Operation CREATE USER failed for 'shopapp'@'localhost'

Das heißt fast immer, dass das Konto bereits existiert, oft als Überbleibsel eines früheren Versuchs. DROP USER 'shopapp'@'localhost'; und noch einmal von vorn.

bind-address: wo die Datenbank überhaupt lauscht

Auf allen vier Distributionen lauscht die Datenbank nach der Paketinstallation nur auf 127.0.0.1. Die Zeile steht bei MariaDB in /etc/mysql/mariadb.conf.d/50-server.cnf und bei MySQL 8.0 in /etc/mysql/mysql.conf.d/mysqld.cnf. Nachsehen statt vermuten:

grep -R "bind-address" /etc/mysql/

Bei MySQL 8.0 gibt es eine zweite Zeile, die gern übersehen wird: mysqlx-bind-address steuert das X-Protokoll auf Port 33060. Wer nur bind-address ändert, öffnet unter Umständen nur den halben Zugang oder lässt den zweiten Port offen.

Die verlässlichste Prüfung ist nicht die Konfigurationsdatei, sondern der Kernel:

ss -lntp
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=712,fd=22))

Steht dort 0.0.0.0:3306 oder *:3306, ist der Dienst im Netz erreichbar. Ergänzend liefert der Server seine eigene Sicht:

mariadb -e "SELECT @@bind_address, @@port, @@skip_networking;"

Zwei Fallen sind hier verbreitet. Die erste: Die Dateien in mariadb.conf.d werden in alphabetischer Reihenfolge gelesen, und die zuletzt gelesene Angabe gewinnt. Wer seine Änderung in eine eigene Datei schreibt, nennt sie deshalb 99-eigene.cnf und nicht 10-eigene.cnf. Genau daran scheitern die meisten Berichte, in denen MariaDB angeblich bind-address ignoriert. Der Vorteil einer eigenen Datei: Paketaktualisierungen fragen nicht nach Konfliktlösung, weil die mitgelieferte Datei unangetastet bleibt.

Die zweite Falle: Wer den Netzwerkzugang gar nicht braucht, geht einen Schritt weiter als bind-address und setzt skip-networking. Dann existiert kein TCP-Port mehr, nur noch der Unix-Socket. Das ist die richtige Einstellung für den Standardfall Webanwendung und Datenbank auf demselben Server, kostet aber Nerven, wenn eine Anwendung in der Konfiguration 127.0.0.1 stehen hat statt localhost: Bei 127.0.0.1 erzwingen die Clientbibliotheken TCP.

Zugriff von außen nur, wenn es wirklich nötig ist

Ein Datenbankport im offenen Netz wird innerhalb von Stunden gefunden und dauerhaft durchprobiert. Die beste Antwort auf die Frage nach externem Zugriff ist deshalb, ihn zu vermeiden. Für gelegentliche Wartung reicht ein SSH-Tunnel, der auf dem eigenen Rechner einen lokalen Port auf den Datenbanksocket der Gegenstelle legt. Das Datenbankwerkzeug verbindet sich dann gegen 127.0.0.1, ohne dass der Server im Netz etwas öffnet.

Für dauerhafte Verbindungen zwischen mehreren Servern ist ein WireGuard-Tunnel die saubere Lösung. Die Datenbank bindet dann ausschließlich an die Tunneladresse, nicht an die öffentliche IP-Adresse.

Wenn es doch ein offener Port sein muss, greifen vier Maßnahmen zusammen. Der Server bindet an genau eine interne Adresse. Die Firewall lässt nur die bekannte Quelladresse durch. Das Datenbankkonto ist an dieselbe Adresse gebunden, also 'shopapp'@'10.0.0.5' und niemals 'shopapp'@'%'. Und die Verbindung wird verschlüsselt erzwungen:

ALTER USER 'shopapp'@'10.0.0.5' REQUIRE SSL;

Beim Testen von außen begegnen einem zwei Fehler, die oft verwechselt werden. Der erste bedeutet, dass niemand antwortet, also Firewall oder bind-address:

ERROR 2003 (HY000): Can't connect to MySQL server on '203.0.113.10:3306' (110)

Der zweite bedeutet, dass der Server antwortet und die Verbindung bewusst ablehnt, also fehlt das passende Konto für diese Herkunft:

ERROR 1130 (HY000): Host '203.0.113.55' is not allowed to connect to this MariaDB server

Lokal wiederum ist der Klassiker, dass der Dienst schlicht nicht läuft:

ERROR 2002 (HY000): Can't connect to local server through socket '/run/mysqld/mysqld.sock' (2)

Passwörter nicht auf der Kommandozeile

Der Aufruf mariadb -u shopapp -pGeheim123 funktioniert und ist trotzdem ein Fehler. Der Server sagt es selbst:

Warning: Using a password on the command line interface can be insecure.

Zwei Gründe stehen dahinter. Erstens landet die Zeile in der Shell-Historie. Zweitens ist die Kommandozeile eines Prozesses auf einem Standardsystem für jeden angemeldeten Benutzer über ps sichtbar. Auf einem Server mit mehreren Kunden oder mehreren Diensten ist das ein Passwortabzug ohne Aufwand.

Der richtige Weg für Menschen ist -p ohne Anhang. Dann wird interaktiv gefragt, und nichts steht in der Historie:

mariadb -u shopapp -p shopdb

Der richtige Weg für Skripte und Cronjobs ist eine Optionsdatei mit engen Rechten. Anlegen mit korrektem Modus in einem Schritt:

install -m 600 /dev/null /root/.my.cnf

Inhalt:

[client]
user=backup
password=HierEinLangesZufallspasswort

Danach findet jeder Client die Zugangsdaten selbst. Für getrennte Aufgaben legt man mehrere Dateien an und verweist gezielt darauf. Dabei gilt eine Regel, an der viele scheitern: --defaults-extra-file und --defaults-file müssen die erste Option des Aufrufs sein, sonst werden sie kommentarlos ignoriert.

mariadb-dump --defaults-extra-file=/root/.my-backup.cnf --single-transaction shopdb

Die Umgebungsvariable MYSQL_PWD ist keine Lösung. Sie steht in /proc und ist damit ähnlich sichtbar wie die Kommandozeile. Unter MySQL 8.0 gibt es zusätzlich mysql_config_editor, das eine Datei ~/.mylogin.cnf schreibt. Der Inhalt ist verschleiert, aber nicht verschlüsselt, und MariaDB kennt das Werkzeug nicht. Auf gemischten Umgebungen ist die einfache Optionsdatei mit Modus 600 die verlässlichere Wahl.

Ein letzter Punkt betrifft Backups. Ein Dump enthält alles, was die Anwendung sieht, und ein Backupkonto braucht dafür keine Schreibrechte. Für mariadb-dump mit --single-transaction genügt in der Regel:

GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES ON shopdb.* TO 'backup'@'localhost';
GRANT PROCESS ON *.* TO 'backup'@'localhost';

Abnahme: woran man erkennt, dass es sitzt

Ein Befehl, der ohne Fehler durchläuft, beweist nichts. Diese sechs Proben beweisen etwas:

  1. Das root-Konto nutzt Socketauthentifizierung: mariadb -e "SELECT User, Host, JSON_VALUE(Priv,'$.plugin') AS plugin, JSON_QUERY(Priv,'$.auth_or') AS auth_or FROM mysql.global_priv;" zeigt bei root ein unix_socket, entweder in der Spalte plugin oder unter auth_or. Die Sicht mysql.user taugt dafür nicht, sie gibt nur die erste Methode aus. Unter MySQL 8.0 ist umgekehrt die Spalte plugin in mysql.user maßgeblich, dort muss auth_socket stehen.
  2. Es gibt keine anonymen Konten: mariadb -e "SELECT user, host FROM mysql.user WHERE user = '';" liefert eine leere Ergebnismenge.
  3. Die Testdatenbank ist weg: mariadb -e "SHOW DATABASES LIKE 'test';" liefert nichts.
  4. Der Port ist zu: ss -lntp zeigt für 3306 entweder gar nichts oder ausschließlich 127.0.0.1.
  5. Das Anwendungskonto ist eingeschränkt: Eine Anmeldung damit zeigt bei SHOW DATABASES; nur die eigene Datenbank, und ein DROP TABLE scheitert.
  6. Kein Passwort im Klartext unterwegs: grep -rs "password" /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ findet nichts, ebenso wenig crontab -l für root und ein Blick in /var/spool/cron/crontabs/, und die Optionsdateien haben Modus 600. Der Schalter -s gehört dazu, weil ein fehlendes Verzeichnis den Aufruf sonst mit Exitcode 2 abbricht: auf Debian 13 existiert /etc/cron.d nach einer reinen Datenbankinstallation nicht, dort ist noch gar kein cron-Paket installiert.

Wer diese sechs Punkte auf einem neuen Server abarbeitet, hat die überwiegende Mehrheit der Angriffe gegen Datenbanken bereits ausgeschlossen, ohne eine einzige zusätzliche Software zu installieren. Der Rest ist Aktualisierungsdisziplin und ein funktionierendes Backup, dessen Wiederherstellung mindestens einmal geprobt wurde.

Häufige Fragen

Brauche ich unter MariaDB überhaupt noch ein root-Passwort?
Auf Debian 12 und 13 sowie Ubuntu 22.04 und 24.04 nicht. Das Konto root@localhost nutzt dort standardmäßig das Plugin unix_socket, die Anmeldung erfolgt also über die Identität des Systembenutzers. Ein zusätzliches Passwort erhöht die Sicherheit nicht, sondern schafft ein weiteres Geheimnis, das verloren gehen oder abfließen kann. Nur wenn ein Werkzeug zwingend eine Passwortanmeldung erwartet, legt man dafür ein eigenes Konto an, statt root umzustellen.
Warum sagt mein Debian, dass es das Paket mysql-server nicht gibt?
Debian liefert seit mehreren Veröffentlichungen kein eigenes Paket mysql-server mehr aus. Weder auf Debian 12 noch auf Debian 13 gibt es dafür eine installierbare Version: apt-cache policy mysql-server meldet dort Candidate: (none) und eine leere Version table, weil der Name nur noch virtuell über mariadb-server existiert. Sichtbar macht das apt-cache showpkg mysql-server. Der Befehl für die Installation lautet apt install -y mariadb-server. Auf Ubuntu 22.04 und 24.04 stehen beide zur Verfügung, MySQL in Version 8.0.46 und MariaDB in 10.6 beziehungsweise 10.11.
Wie komme ich wieder in die Datenbank, wenn ich mich ausgesperrt habe?
Bei MariaDB stoppt man den Dienst, setzt per systemctl set-environment die Variable MYSQLD_OPTS auf --skip-grant-tables --skip-networking und startet neu. Nach der Anmeldung ist zuerst FLUSH PRIVILEGES nötig, danach lässt sich das Konto mit ALTER USER zurücksetzen. Anschließend die Variable mit systemctl unset-environment wieder entfernen. Bei MySQL 8.0 funktioniert das nicht, dort nutzt man eine Startdatei über --init-file, die wegen AppArmor in /var/lib/mysql-files liegen muss.
Was bedeutet ERROR 1698 gegenüber ERROR 1045?
ERROR 1698 (28000) bedeutet, dass das Konto Socketauthentifizierung erwartet und der aufrufende Systembenutzer nicht passt. Hier hilft meist sudo vor dem Befehl. ERROR 1045 (28000) mit dem Zusatz (using password: YES) bedeutet dagegen, dass eine Passwortanmeldung versucht wurde und das Passwort nicht stimmt. Die beiden Fehler haben also völlig unterschiedliche Ursachen und Lösungen.
Reicht bind-address = 127.0.0.1 aus, um die Datenbank abzusichern?
Es ist der wichtigste Schritt, aber nicht der einzige. Bei MySQL 8.0 gibt es zusätzlich mysqlx-bind-address für Port 33060, das gesondert gesetzt werden muss. Außerdem gewinnt in /etc/mysql/mariadb.conf.d die zuletzt gelesene Datei, eigene Änderungen gehören deshalb in eine Datei mit hoher Nummer wie 99-eigene.cnf. Geprüft wird das Ergebnis immer mit ss -lntp, nicht in der Konfigurationsdatei.
Warum soll die Anwendung nicht als root auf die Datenbank zugreifen?
Weil damit jede Lücke in der Anwendung zu vollem Zugriff auf alle Datenbanken führt, inklusive der Rechtetabellen. Ein Konto mit GRANT SELECT, INSERT, UPDATE, DELETE ON meinedb.* kann keine Tabelle löschen, keine fremde Datenbank lesen und keine Rechte vergeben. Schemaänderungen laufen über ein getrenntes Konto, das nur beim Deployment verwendet wird.
Wie übergebe ich einem Cronjob ein Datenbankpasswort sicher?
Über eine Optionsdatei mit Modus 600, angelegt etwa mit install -m 600 /dev/null /root/.my.cnf und einem Abschnitt [client] mit user und password. Der Aufruf verweist dann mit --defaults-extra-file darauf, und zwar zwingend als erste Option, sonst wird der Schalter ignoriert. Passwörter direkt hinter -p auf der Kommandozeile sind für jeden Benutzer über ps sichtbar, MYSQL_PWD ist über /proc ähnlich einsehbar.

MariaDB MySQL Datenbank Serversicherheit Debian Ubuntu Linux-Administration