Linux Server
Administration
Von der ersten SSH-Verbindung bis zur Produktionsumgebung. Praxisnah, in klarer Sprache, mit Schritt-für-Schritt-Aufgaben.
Arbeite die Module der Reihe nach durch – jedes baut auf dem vorherigen auf. Unbekannte Begriffe erklärt das Glossar. Die orangefarbenen Praxisaufgaben am Ende jedes Moduls sind der wichtigste Teil – ohne Übung bleibt kaum etwas hängen. Die violetten KI-Hinweise zeigen, wann es sich lohnt, Claude oder ChatGPT zu fragen.
Glossar
Alle wichtigen Abkürzungen und Fachbegriffe erklärt. Im Kompendium sind Begriffe mit gestrichelter Unterlinie direkt erklärbar – einfach mit der Maus drüberfahren.
Grundlagen der Netzwerktechnik
Bevor du einen Server verwaltest, musst du verstehen, wie Netzwerke funktionieren. Dieses Modul erklärt die wichtigsten Grundbausteine: IP-Adressen, Ports, DNS und Routing – die Sprache, in der Computer weltweit miteinander reden.
Jedes Gerät in einem Netzwerk – ob Laptop, Server oder Smartphone – braucht eine eindeutige Adresse, damit Datenpakete ankommen. Diese Adresse heißt IP-Adresse. Es gibt zwei Versionen:
- IPv4 – vier Zahlen von 0 bis 255, getrennt durch Punkte. Beispiel:
192.168.1.100 - IPv6 – acht Blöcke aus Hexadezimalziffern. Beispiel:
2001:0db8:85a3::8a2e:0370:7334
IPv4 ist noch immer am weitesten verbreitet. IPv6 wurde eingeführt, weil es von IPv4 nur rund 4 Milliarden Adressen gibt – viel zu wenig für die Milliarden vernetzter Geräte heute.
Private vs. öffentliche IP-Adressen
Nicht jede IP-Adresse ist im Internet erreichbar. Einige Bereiche sind für private Netzwerke reserviert – etwa dein Heimnetz oder das Firmennetzwerk:
| Bereich | Beispiel | Typischer Einsatz |
|---|---|---|
10.0.0.0/8 | 10.0.0.1 | Große Firmen- und Rechenzentrumsnetze |
172.16.0.0/12 | 172.16.0.1 | Mittlere Netze, VMs, Docker |
192.168.0.0/16 | 192.168.1.1 | Heimnetzwerke, Router, kleine Büros |
Alles außerhalb dieser Bereiche ist eine öffentliche IP-Adresse – über sie ist ein Server direkt aus dem Internet erreichbar.
Im Terminal: ip addr show zeigt alle IP-Adressen deines Rechners. Deine öffentliche IP findest du z.B. mit curl ifconfig.me.
Subnetting und CIDR-Schreibweise
Die Zahl hinter dem Schrägstrich (z.B. /24) gibt an, wie viele Bits die Netzwerkmaske umfasst – also wie groß das Netzwerk ist. Je größer die Zahl, desto kleiner das Netz:
| CIDR | Netzmaske | Nutzbare Adressen | Typischer Einsatz |
|---|---|---|---|
/8 | 255.0.0.0 | 16.777.214 | Sehr große Netze |
/16 | 255.255.0.0 | 65.534 | Mittelgroße Firmen |
/24 | 255.255.255.0 | 254 | Heimnetz, kleines Büro |
/30 | 255.255.255.252 | 2 | Punkt-zu-Punkt-Links |
/32 | 255.255.255.255 | 1 (nur diese IP) | Firewall-Regeln |
Stell dir das Netzwerk wie eine Straße vor: /24 ist eine Straße mit 254 Häusern. /25 teilt diese Straße in zwei gleiche Hälften. /32 ist genau ein einzelnes Haus.
Ein Server hat eine IP-Adresse, aber gleichzeitig laufen darauf viele verschiedene Dienste: Webserver, SSH, Datenbank, Mail. Ports trennen diese Dienste voneinander.
Stell dir vor: Die IP-Adresse ist das Gebäude, der Port ist die Büronummer. Wenn du 192.168.1.50:22 aufrufst, erreichst du genau den SSH-Dienst auf diesem Server.
| Port | Protokoll | Dienst | Wofür |
|---|---|---|---|
22 | TCP | SSH | Sichere Fernverwaltung |
80 | TCP | HTTP | Unverschlüsselte Webseiten |
443 | TCP | HTTPS | Verschlüsselte Webseiten |
21 | TCP | FTP | Dateiübertragung (veraltet) |
25 | TCP | SMTP | E-Mails senden |
53 | TCP/UDP | DNS | Namensauflösung |
3306 | TCP | MySQL/MariaDB | Datenbank-Verbindungen |
5432 | TCP | PostgreSQL | Datenbank-Verbindungen |
8080 | TCP | HTTP (alternativ) | Webserver, Dev-Server |
Ports von 1 bis 1023 heißen Well-Known Ports. Ein normaler Benutzer darf sie nicht öffnen – dafür braucht es Root-Rechte. Ports von 1024 bis 65535 kann jeder Nutzer frei verwenden.
Kein Mensch merkt sich IP-Adressen. DNS (Domain Name System) übersetzt lesbare Namen wie google.com in IP-Adressen wie 142.250.185.78 – vollautomatisch, im Hintergrund, bei jeder Anfrage.
Wie DNS-Auflösung funktioniert
- Du tippst
google.comin den Browser - Dein Rechner fragt seinen konfigurierten DNS-Resolver (meistens der Router oder ein externer wie
1.1.1.1) - Der Resolver fragt ggf. weiter: Root-Nameserver → TLD-Nameserver (
.com) → Google-Nameserver - Die IP-Adresse kommt zurück und wird kurzzeitig im Cache gespeichert (TTL)
Die wichtigsten DNS-Eintragstypen
| Typ | Bedeutung | Beispiel |
|---|---|---|
A | Domain → IPv4-Adresse | example.com → 1.2.3.4 |
AAAA | Domain → IPv6-Adresse | example.com → 2001:db8::1 |
CNAME | Alias → andere Domain | www → example.com |
MX | Mail-Server für diese Domain | mail.example.com |
PTR | IP → Domain (Reverse DNS) | 1.2.3.4 → example.com |
TXT | Freitext (SPF, DKIM, Verifikation) | v=spf1 include:… |
NS | Zuständige Nameserver | ns1.example.com |
Jeder DNS-Eintrag hat eine TTL (Sekunden). Solange er im Cache liegt, wird nicht erneut gefragt. Ein niedriger TTL (z.B. 60) ist praktisch beim DNS-Wechsel, ein hoher TTL (z.B. 86400 = 1 Tag) reduziert Last. Nach einem DNS-Wechsel kann es bis zur vollen TTL dauern, bis alle ihn sehen.
Wenn dein Computer ein Paket sendet, muss er wissen, wohin. Das erledigt die Routing-Tabelle. Der wichtigste Eintrag ist das Standard-Gateway – der Router, an den alles weitergeleitet wird, was nicht im lokalen Netz liegt.
ip route show
# Typische Ausgabe:
default via 192.168.1.1 dev eth0 # → Standard-Gateway (alles unbekannte geht hier hin)
192.168.1.0/24 dev eth0 proto kernel # → lokales Netz direkt erreichbar Pakete an 192.168.1.x gehen direkt ans Ziel. Alles andere (z.B. 8.8.8.8) geht zum Gateway (192.168.1.1), der es dann weiterschickt.
Diese Werkzeuge brauchst du täglich. Lerne sie auswendig – sie helfen dir, Probleme schnell einzugrenzen.
Erreichbarkeit und Verbindung testen
ping 8.8.8.8 # Pakete senden, Antwortzeit messen (Ctrl+C zum Stoppen)
ping -c 4 google.com # nur 4 Pakete senden (dann automatisch stoppen)
ping -i 0.2 192.168.1.1 # schneller pingen (alle 0,2 Sekunden) traceroute google.com # alle Zwischenstationen (Hops) anzeigen
mtr google.com # interaktive Kombination aus ping + traceroute
# (installieren: sudo apt install mtr) DNS abfragen
dig google.com # A-Eintrag abfragen (vollständige Ausgabe)
dig +short google.com # nur die IP-Adresse ausgeben
dig google.com MX # Mail-Server-Einträge abfragen
dig google.com ANY # alle Eintragstypen abfragen
dig -x 8.8.8.8 # Reverse-Lookup: IP → Domain
dig @1.1.1.1 heise.de # bestimmten DNS-Server direkt befragen
nslookup google.com # einfachere Alternative zu dig IP-Adressen und Interfaces
ip addr show # alle Interfaces mit IP-Adressen
ip addr show eth0 # nur das Interface eth0
ip route show # Routing-Tabelle
ip link show # Status aller Interfaces (UP / DOWN) Offene Ports und aktive Verbindungen
ss -tulpn # alle lauschenden Ports anzeigen (wichtigster Befehl!)
# -t = TCP, -u = UDP, -l = lauschend, -p = Prozess, -n = Nummern
ss -tulpn | grep :22 # nur SSH-Port filtern
ss -tulpn | grep :80 # nur HTTP-Port filtern
netstat -tulpn # ältere Alternative zu ss (evtl. nachinstallieren) Netid State Recv-Q Send-Q Local Address:Port Process
tcp LISTEN 0 128 0.0.0.0:22 sshd
tcp LISTEN 0 511 0.0.0.0:80 nginx
tcp LISTEN 0 511 0.0.0.0:443 nginx
tcp LISTEN 0 128 127.0.0.1:3306 mysqld Diese Ausgabe liest sich so: SSH (:22) und nginx (:80, :443) sind von überall erreichbar (0.0.0.0). MySQL (:3306) lauscht nur auf 127.0.0.1 – also nur lokal, nicht von außen. Genau so soll es sein!
Prüfe mit ss -tulpn regelmäßig, welche Ports offen sind. Alles was nicht gebraucht wird, sollte nicht lauschen. Ein Port, der auf 0.0.0.0 lauscht, ist aus dem Internet erreichbar (sofern keine Firewall dahinter ist).
Wenn du eine Ausgabe von ss -tulpn, ip route show oder dig nicht verstehst, kopiere sie einfach in Claude oder ChatGPT und schreib dazu: „Erkläre mir diese Ausgabe Zeile für Zeile auf Deutsch." Das spart viel Recherche-Zeit und du lernst dabei mehr als aus trockener Dokumentation.
In dieser Aufgabe schaust du dir an, wie dein System gerade ins Netzwerk eingebunden ist. Öffne ein Terminal auf deiner Linux-VM oder deinem Server.
ip addr show. Welche IP-Adresse hat dein Haupt-Interface (z.B. eth0 oder enp3s0)? Notiere sie.ip route show. Welche IP-Adresse steht nach default via? Das ist dein Standard-Gateway.ping -c 4 <gateway-ip>. Wie groß ist die durchschnittliche Antwortzeit (RTT)? Unter 1 ms ist normal bei lokalem Netz.ping -c 4 8.8.8.8. Funktioniert es? Falls nicht, liegt das Problem zwischen deinem Rechner und dem Internet.ss -tulpn. Welche Dienste laufen auf deinem System? Erkennst du SSH (:22)?Lösungshinweise anzeigen
Bei ip addr suchst du nach Zeilen wie inet 192.168.1.50/24 – das ist deine IP mit Subnetzmaske. Die Zeile beginnt mit inet (nicht inet6).
Bei ip route ist die Zeile default via X.X.X.X dein Gateway. Bei einem typischen Heimnetz endet die IP auf .1, z.B. 192.168.1.1.
Die RTT beim lokalen Ping sollte unter 1 ms liegen. Zu 8.8.8.8 (Google) sind 10–30 ms in Deutschland normal.
Lerne, wie DNS-Einträge aufgebaut sind, indem du sie für echte Domains abfragst.
heise.de ab: dig +short heise.de. Du bekommst eine oder mehrere IP-Adressen zurück.gmail.com ab: dig gmail.com MX. Siehst du die MX-Einträge in der ANSWER SECTION? Die Zahlen davor sind Prioritäten (kleinere = bevorzugter).dig -x 8.8.8.8. Welchen Namen hat Googles DNS-Server? (Tipp: Der Eintrag steht im PTR-Record)dig @1.1.1.1 heise.de. Vergleiche das Ergebnis mit dem ohne @1.1.1.1. Unterscheiden sich die IPs?+short): dig heise.de. Finde den TTL-Wert in der ANSWER SECTION. In wie vielen Sekunden verfällt dieser Cache-Eintrag?Lösungshinweise anzeigen
Der Reverse-Lookup von 8.8.8.8 ergibt dns.google – so heißt Googles öffentlicher DNS-Server offiziell.
Der TTL-Wert steht in der ANSWER SECTION als Zahl in der zweiten Spalte, z.B. heise.de. 3600 IN A 193.99.144.80 – hier ist der TTL 3600 Sekunden (= 1 Stunde).
In dieser Aufgabe siehst du, welchen Weg deine Datenpakete durchs Internet nehmen.
mtr falls noch nicht vorhanden: sudo apt install mtrmtr google.com. Du siehst jeden Router auf dem Weg. Drücke q zum Beenden.mtr 1.1.1.1 (Cloudflare-DNS). Wie viele Hops sind es? Wo unterscheidet sich der Weg von mtr google.com?Lösungshinweise anzeigen
Typisch sind 10–15 Hops bis zu einem großen Ziel. Die ersten 3–4 Hops sind meist in deinem lokalen Netz und sehr schnell (<5 ms). Danach kommen Provider-Router, dann Internet-Austauschpunkte (IXPs).
Wenn ein Hop ??? oder 100% Paketverlust zeigt, bedeutet das nicht zwingend ein Problem – viele Router antworten aus Sicherheitsgründen nicht auf ICMP-Anfragen, leiten aber regulären Traffic durch.
Subnetting (die Berechnung von IP-Bereichen aus CIDR-Notationen) ist zunächst verwirrend. Frag eine KI: „Erkläre mir Schritt für Schritt, was das Netz 10.0.5.0/26 bedeutet: Wie viele Hosts passen rein? Was ist die erste und letzte nutzbare IP? Was ist die Broadcast-Adresse?" – Lass dir dann eigene Beispiele zum Üben geben.
SSH -- Sicherer Fernzugriff
Server stehen selten neben dir auf dem Schreibtisch. Du verwaltest sie aus der Ferne -- per SSH. In diesem Modul lernst du, wie du dich sicher verbindest, Schlüsselpaare einrichtest und den SSH-Dienst absicherst.
Bei SSH arbeitest du immer an zwei Orten gleichzeitig: deinem eigenen Rechner und dem Server. Stell dir vor, du rufst einen Kollegen an und gibst ihm Anweisungen -- du sitzt an deinem Schreibtisch, er sitzt am anderen Ende der Leitung. Was du tippst, führt er aus. Ab jetzt markieren wir in diesem Modul immer klar, wo du dich befindest:
Befehle, die du in deinem eigenen Terminal eingibst -- auf deinem Laptop oder PC.
Befehle, die auf dem entfernten Server laufen -- erst nach dem SSH-Login erreichbar.
SSH (Secure Shell) ist ein Protokoll, mit dem du dich verschlüsselt auf einem entfernten Server anmelden und ihn bedienen kannst -- als wärst du direkt davor. Alles, was du tippst, und alles, was der Server antwortet, ist verschlüsselt. Niemand kann mitlesen.
Aus Modul 01 weißt du bereits: SSH läuft standardmäßig auf Port 22 (TCP). Der Server wartet dort auf Verbindungen, und du verbindest dich von deinem Rechner aus mit einem SSH-Client.
Auf Linux und macOS ist der SSH-Client bereits vorinstalliert. Unter Windows 10/11 ebenfalls (PowerShell oder Windows Terminal). Du brauchst nichts zu installieren.
Die einfachste Verbindung sieht so aus -- du tippst das auf deinem eigenen Rechner:
Ersetze benutzername und 192.168.1.50 durch die echten Daten deines Servers.
ssh benutzername@192.168.1.50
# Beim ersten Mal kommt eine Fingerprint-Warnung -- mit "yes" bestätigen
# Danach dein Passwort eingeben (Zeichen werden NICHT angezeigt) Du kannst statt der IP-Adresse auch einen Hostnamen verwenden, wenn DNS funktioniert:
ssh admin@mein-server.local Manchmal läuft SSH nicht auf dem Standard-Port 22. Dann gibst du den Port mit -p an:
ssh -p 2222 admin@192.168.1.50 Beim allerersten Verbinden fragt SSH: "Are you sure you want to continue connecting?". Das ist normal -- SSH kennt den Server noch nicht. Bestätige mit yes. Der Fingerprint wird dann in ~/.ssh/known_hosts auf deinem Rechner gespeichert. Kommt die Warnung später nochmal, könnte jemand den Server fälschen -- dann Vorsicht!
Passwörter sind umständlich und unsicher. Die bessere Methode ist ein SSH-Schlüsselpaar. Du hast zwei Dateien:
- Privater Schlüssel (
id_ed25519) -- bleibt auf deinem Rechner. Niemals weitergeben! - Öffentlicher Schlüssel (
id_ed25519.pub) -- wird auf den Server kopiert. Kann bedenkenlos geteilt werden.
Stell dir das wie ein Schloss mit Schlüssel vor: Den öffentlichen Schlüssel (das Schloss) hängst du an den Server. Den privaten Schlüssel (der echte Schlüssel) behältst du bei dir.
Schlüsselpaar erstellen
Dieser Befehl erstellt dein Schlüsselpaar. Er läuft auf deinem eigenen Computer -- nicht auf dem Server.
ssh-keygen -t ed25519 -C "mein-laptop"
# -t ed25519 = moderner, sicherer Algorithmus
# -C = Kommentar (hilft beim Zuordnen)
# Du wirst gefragt:
# 1. Speicherort (Enter = Standard: ~/.ssh/id_ed25519)
# 2. Passphrase (optional, aber empfohlen -- schützt den Schlüssel) Nach dem Erstellen findest du zwei Dateien auf deinem Rechner:
| Datei | Typ | Bedeutung |
|---|---|---|
~/.ssh/id_ed25519 | Privat | Dein geheimer Schlüssel -- nie teilen! |
~/.ssh/id_ed25519.pub | Öffentlich | Wird auf den Server kopiert |
Der private Schlüssel (id_ed25519 ohne .pub) ist wie dein Haustürschlüssel. Wer ihn hat, kommt auf alle Server, für die er eingerichtet ist. Teile ihn nicht per E-Mail, Chat oder Cloud. Setze eine starke Passphrase.
Öffentlichen Schlüssel auf den Server kopieren
ssh-copy-id läuft auf deinem Rechner und verbindet sich einmalig mit dem Server, um den Schlüssel zu übertragen. Du brauchst dafür noch einmal dein Passwort.
ssh-copy-id admin@192.168.1.50
# Fragt einmalig das Passwort ab
# Kopiert deinen öffentlichen Schlüssel auf den Server Ab jetzt kannst du dich ohne Passwort anmelden:
ssh admin@192.168.1.50
# Keine Passwort-Abfrage mehr! (nur ggf. die Passphrase des Schlüssels) ssh-copy-id fügt deinen öffentlichen Schlüssel in die Datei ~/.ssh/authorized_keys auf dem Server ein. Das ~ steht hier für das Home-Verzeichnis des Benutzers auf dem Server -- nicht auf deinem Rechner. Du könntest das auch manuell tun: nach dem SSH-Login den Inhalt von id_ed25519.pub in die Datei auf dem Server einfügen. ssh-copy-id macht es nur bequemer.
Überprüfen: Schlüssel auf dem Server angekommen?
Verbinde dich zuerst mit ssh admin@192.168.1.50 auf den Server. Sobald du eingeloggt bist, prüfe den Inhalt der authorized_keys-Datei:
cat ~/.ssh/authorized_keys
# Du solltest eine Zeile sehen, die mit "ssh-ed25519" beginnt
# und mit deinem Kommentar "mein-laptop" endet Anschließend Verbindung beenden:
exit Um eine Textdatei anzulegen oder zu bearbeiten, nutzen wir hier nano -- einen einfachen Editor, der direkt im Terminal läuft. Du brauchst für nano kein Vorwissen:
nano DATEINAME-- Datei öffnen (wird neu erstellt, falls sie noch nicht existiert)- Text eingeben und bearbeiten wie in einem normalen Texteditor
Ctrl+OdannEnter-- Datei speichernCtrl+X-- nano schließen
In Modul 03 lernst du ausführlich, wie du mit Texteditoren im Terminal arbeitest.
Wenn du dich täglich auf mehrere Server verbindest, wird das Tippen von Benutzernamen, IP-Adressen und Ports schnell lästig. Die SSH-Config-Datei auf deinem Rechner ist deine Abkürzung.
Öffne die Datei mit nano (sie wird neu angelegt, falls sie noch nicht existiert):
nano ~/.ssh/config Füge folgenden Inhalt ein -- passe IP-Adresse und Benutzername an deinen Server an:
# Datei: ~/.ssh/config -- liegt auf DEINEM Rechner
Host webserver
HostName 192.168.1.50
User admin
Port 22
Host backup
HostName 10.0.0.200
User root
Port 2222
IdentityFile ~/.ssh/id_backup Danach verbindest du dich einfach mit dem Kurznamen:
ssh webserver # statt: ssh admin@192.168.1.50
ssh backup # statt: ssh -p 2222 root@10.0.0.200 | Option | Bedeutung |
|---|---|
Host | Dein frei wählbarer Kurzname |
HostName | IP-Adresse oder Domain des Servers |
User | Benutzername auf dem Server |
Port | SSH-Port (Standard: 22) |
IdentityFile | Pfad zu einem bestimmten privaten Schlüssel |
SSH kann nicht nur Befehle ausführen, sondern auch Dateien kopieren. Das Werkzeug heißt SCP (Secure Copy). Alle SCP-Befehle gibst du auf deinem Rechner ein -- SCP kümmert sich selbst um die Verbindung zum Server.
# Datei von deinem Rechner → Server
scp datei.txt admin@192.168.1.50:/home/admin/
# Datei vom Server → deinen Rechner
scp admin@192.168.1.50:/var/log/syslog ./syslog-kopie.txt
# Ganzen Ordner kopieren (rekursiv)
scp -r mein-ordner/ admin@192.168.1.50:/tmp/ Wenn du die SSH-Config eingerichtet hast, funktionieren auch die Kurznamen:
scp datei.txt webserver:/home/admin/ Manchmal läuft ein Dienst auf einem Server, der nur lokal erreichbar ist -- z.B. eine Datenbank auf 127.0.0.1:3306. Mit einem SSH-Tunnel leitest du diesen Port verschlüsselt auf deinen Rechner um.
Dieser Befehl baut den Tunnel auf -- du tippst ihn auf deinem Rechner. SSH verbindet sich mit dem Server und leitet den dortigen Port zu dir durch.
ssh -L 3306:127.0.0.1:3306 admin@192.168.1.50
# -L = Local Tunnel
# 3306 (links) = Port auf DEINEM Rechner
# 127.0.0.1:3306 (rechts) = Ziel auf dem SERVER Jetzt kannst du auf deinem Rechner localhost:3306 aufrufen und erreichst die Datenbank auf dem Server -- verschlüsselt durch den SSH-Tunnel.
Immer wenn ein Dienst nur auf 127.0.0.1 lauscht (sichtbar in ss -tulpn aus Modul 01) und du ihn von deinem Rechner aus nutzen willst. Typisch: Datenbanken, Admin-Panels, interne Webseiten.
Der SSH-Server hat eine Konfigurationsdatei: /etc/ssh/sshd_config. Sie liegt auf dem Server -- öffne sie nach dem SSH-Login mit nano:
sudo nano /etc/ssh/sshd_config
# sudo ist nötig, weil die Datei root gehört
# Speichern: Ctrl+O → Enter · Schließen: Ctrl+X Suche in der Datei die folgenden Einstellungen und passe sie an (oder füge sie hinzu, falls sie fehlen):
# Root-Login verbieten (immer als normaler User anmelden, dann sudo)
PermitRootLogin no
# Passwort-Anmeldung deaktivieren (nur noch Schlüssel erlauben)
PasswordAuthentication no
# Nur bestimmte Benutzer erlauben
AllowUsers admin deploy
# Port ändern (optional, reduziert automatisierte Angriffe)
Port 2222
# Leere Passwörter verbieten
PermitEmptyPasswords no Nach Änderungen musst du den SSH-Dienst neu laden. Das reload statt restart ist wichtig -- bestehende Verbindungen bleiben so erhalten:
sudo systemctl reload sshd Bevor du PasswordAuthentication no setzt, stelle sicher, dass dein SSH-Schlüssel funktioniert! Teste die Schlüssel-Anmeldung in einer zweiten, parallelen SSH-Sitzung (zweites Terminalfenster öffnen, neu verbinden), bevor du die Passwort-Anmeldung deaktivierst. Sonst kommst du nicht mehr auf den Server.
1. Schlüsselpaar erstellen. 2. Öffentlichen Schlüssel auf den Server kopieren. 3. Schlüssel-Login testen (zweites Terminal!). 4. Erst dann Passwort-Login deaktivieren. Niemals Schritt 4 vor Schritt 3!
Die Datei /etc/ssh/sshd_config hat viele Optionen. Wenn du dir unsicher bist, was eine Einstellung bewirkt, kopiere die relevanten Zeilen und frag: "Erkläre mir diese sshd_config-Zeilen auf Deutsch. Was bewirkt jede Einstellung und ist sie sicher?"
Befehl direkt auf dem Server ausführen
Du kannst einen einzelnen Befehl auf dem Server ausführen, ohne eine interaktive SSH-Sitzung zu öffnen. Der Befehl wird in Anführungszeichen nach der Server-Adresse angegeben:
ssh webserver "df -h" # Festplattenplatz anzeigen
ssh webserver "cat /etc/hostname" # Hostnamen abfragen SSH-Verbindung am Leben halten
Wenn die Verbindung nach Inaktivität abbricht, füge in deine ~/.ssh/config auf deinem Rechner ein:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3 SSH sendet dann alle 60 Sekunden ein Keep-Alive-Signal.
Mehrere Schlüssel verwalten
Für verschiedene Server kannst du verschiedene Schlüssel erstellen und in der SSH-Config zuweisen:
ssh-keygen -t ed25519 -f ~/.ssh/id_arbeit -C "arbeit-server"
ssh-keygen -t ed25519 -f ~/.ssh/id_privat -C "privat-server" In der SSH-Config weist du dann jedem Host den passenden Schlüssel zu (mit IdentityFile).
Du verbindest dich zum ersten Mal per SSH mit einem Server oder einer virtuellen Maschine in deinem Netzwerk. Dafür brauchst du die IP-Adresse des Servers und einen Benutzernamen.
ssh benutzername@IP-ADRESSE. Bestätige den Fingerprint mit yes und gib dein Passwort ein.whoami ein (zeigt deinen Benutzernamen auf dem Server) und dann hostname (zeigt den Namen des Servers). Beide Ausgaben sollten sich von deinem lokalen Rechner unterscheiden.exit, um die Verbindung zu trennen, oder öffne ein zweites Terminalcat ~/.ssh/known_hosts. Diese Datei liegt auf deinem Rechner und enthält einen Eintrag pro bekanntem Server.ssh benutzername@IP-ADRESSE. Diesmal kommt keine Fingerprint-Warnung mehr -- SSH erkennt den Server wieder.Lösungshinweise anzeigen
whoami zeigt den Benutzernamen, mit dem du auf dem Server angemeldet bist. hostname zeigt den Servernamen. Beides sollte sich von deinem lokalen Rechner unterscheiden -- das bestätigt, dass du wirklich auf dem Server bist.
In ~/.ssh/known_hosts auf deinem Rechner steht eine lange Zeile pro bekanntem Server mit IP-Adresse und Fingerprint.
Du richtest eine schlüsselbasierte Anmeldung ein -- die sicherste und bequemste Methode. Alle Schritte bis auf Schritt 5 laufen auf deinem eigenen Rechner.
ssh-keygen -t ed25519 -C "mein-rechner". Wähle eine Passphrase (mindestens 8 Zeichen).ls -la ~/.ssh/id_ed25519*. Du solltest zwei Dateien sehen: die ohne Endung (privat) und die mit .pub (öffentlich).ssh-copy-id benutzername@IP-ADRESSE. Dieser Befehl läuft auf deinem Rechner und fragt einmalig dein Passwort ab.ssh benutzername@IP-ADRESSE. Du solltest kein Server-Passwort mehr eingeben müssen -- nur ggf. die Passphrase deines Schlüssels.~/.ssh/authorized_keys: cat ~/.ssh/authorized_keys. Findest du deinen Kommentar "mein-rechner" am Ende der Zeile?exit um den Server zu verlassenLösungshinweise anzeigen
Nach ssh-keygen siehst du auf deinem Rechner zwei Dateien: id_ed25519 (privat, Rechte 600) und id_ed25519.pub (öffentlich).
Die Datei ~/.ssh/authorized_keys auf dem Server enthält genau den Inhalt deiner .pub-Datei -- eine Zeile beginnend mit ssh-ed25519 und endend mit mein-rechner.
Du richtest eine SSH-Config ein und überträgst Dateien per SCP. Alle Schritte laufen auf deinem Rechner -- SCP und SSH kümmern sich selbst um die Verbindung zum Server.
nano ~/.ssh/config. Füge folgenden Block ein (IP und Benutzername anpassen) und speichere mit Ctrl+O → Enter, schließe mit Ctrl+X:Host mein-server
HostName 192.168.1.50
User admin
Port 22ssh dein-kurzname. Du solltest direkt verbunden werden -- ohne IP-Adresse einzutippen. Tippe exit um die Verbindung wieder zu trennen.echo "Hallo vom Laptop" > test.txt. Der Befehl echo gibt Text aus, das > leitet die Ausgabe in eine Datei um -- sie wird neu erstellt. Du solltest keine Ausgabe sehen; die Datei entsteht still im Hintergrund.scp test.txt dein-kurzname:/tmp/. SCP läuft auf deinem Rechner und überträgt die Datei automatisch.ssh dein-kurzname "cat /tmp/test.txt". Dieser Befehl wird auf deinem Rechner eingegeben, führt aber cat auf dem Server aus und zeigt dir das Ergebnis.Lösungshinweise anzeigen
Die SSH-Config muss Rechte 600 oder 644 haben. Falls der Kurzname nicht funktioniert, prüfe Einrückung (4 Leerzeichen) und Schreibweise in der Datei -- Groß-/Kleinschreibung bei Host, HostName usw. beachten.
Schritt 5 zeigt, wie SSH als "Fernsteuerung" funktioniert: du tippst lokal, der Server führt aus und schickt die Ausgabe zurück zu dir.
SSH-Verbindungen schlagen oft mit kryptischen Meldungen fehl: "Permission denied (publickey)", "Connection refused" oder "Host key verification failed". Kopiere die Fehlermeldung und frag: "Ich bekomme diesen SSH-Fehler. Was bedeutet er und wie behebe ich ihn? Mein Setup: Ubuntu 24.04, Schlüssel-Authentifizierung."
Bash & Editor vim
Das Terminal ist dein wichtigstes Werkzeug auf dem Server. In diesem Modul lernst du, wie du dich im Linux-Dateisystem bewegst, Dateien und Texte verwaltest und mit dem Editor vim Konfigurationsdateien bearbeitest -- alles direkt auf der Kommandozeile.
In Modul 02 hast du gelernt, wie du dich per SSH mit dem Server verbindest. Ab dem Moment, wo du eingeloggt bist, läuft alles in diesem Modul auf dem Server. Du schreibst in deinem Terminal -- dein Rechner zeigt dir die Ausgabe an --, aber die Befehle werden auf dem Server ausgeführt.
Melde dich zuerst per SSH auf deinem Server an: ssh benutzername@IP-ADRESSE. Sobald du den Prompt siehst (z.B. admin@server01:~$), bist du bereit. Alle folgenden Befehle führst du in dieser Sitzung aus.
Nach dem SSH-Login landest du in der Shell. Die Shell ist ein Programm, das deine Eingaben entgegennimmt, ausführt und dir das Ergebnis zurückgibt. Auf Debian/Ubuntu heißt sie Bash (Bourne Again Shell). Stell dir die Shell wie das Armaturenbrett in einem Auto vor: erst fremd, aber sobald du die wichtigsten Elemente kennst, willst du nicht mehr zurück.
Der Prompt zeigt dir immer, wer du bist und wo du dich befindest:
admin@server01:~$
# admin = dein Benutzername
# server01 = Hostname des Servers
# ~ = aktuelles Verzeichnis (~ ist Abkürzung für dein Home-Verzeichnis)
# $ = normaler Benutzer (# bedeutet: du bist root) Linux hat keine Laufwerksbuchstaben wie Windows (C:, D:). Alles hängt an einem einzigen Stammverzeichnis: /. Stell es dir wie einen Baum vor -- der Stamm ist /, alle Äste (Ordner) wachsen davon ab.
pwd # Aktuelles Verzeichnis anzeigen (Print Working Directory)
ls # Inhalt des aktuellen Verzeichnisses auflisten
ls -la # Detailliert inkl. versteckter Dateien (beginnen mit .)
ls -lh # Mit lesbaren Dateigrößen (KB, MB, GB)
cd /var/log # In ein anderes Verzeichnis wechseln
cd .. # Ein Verzeichnis nach oben gehen
cd # Immer zurück ins Home-Verzeichnis (~)
cd - # Ins zuletzt besuchte Verzeichnis wechseln Wichtige Verzeichnisse kennen
| Pfad | Was ist dort |
|---|---|
/ | Stammverzeichnis -- der Anfang von allem |
/home/admin | Dein persönliches Home-Verzeichnis (Abkürzung: ~) |
/etc | Konfigurationsdateien des Systems und der Dienste |
/var/log | Log-Dateien -- hier steht, was auf dem System passiert |
/tmp | Temporäre Dateien -- werden beim Neustart gelöscht |
/usr/bin | Installierte Programme und Befehle |
/root | Home-Verzeichnis des root-Benutzers |
Drücke nach den ersten Buchstaben eines Befehls oder Pfades die Tab-Taste. Die Shell vervollständigt automatisch. Zweimal Tab zeigt alle Möglichkeiten. Das spart enorm viel Tipparbeit und verhindert Tippfehler.
cat datei.txt # Gesamten Inhalt anzeigen
less /var/log/syslog # Große Datei seitenweise lesen (q = beenden)
head -20 datei.txt # Erste 20 Zeilen anzeigen
tail -20 datei.txt # Letzte 20 Zeilen anzeigen
tail -f /var/log/syslog # Live: neue Zeilen in Echtzeit (Ctrl+C = beenden) mkdir projekte # Neuen Ordner erstellen
mkdir -p a/b/c # Verschachtelte Ordner auf einmal erstellen
touch datei.txt # Leere Datei erstellen
echo "Hallo" > datei.txt # Text in Datei schreiben (überschreibt!)
echo "Mehr" >> datei.txt # Text an Datei anhängen cp datei.txt kopie.txt # Datei kopieren
cp -r ordner/ backup/ # Ganzen Ordner rekursiv kopieren
mv datei.txt neu.txt # Datei umbenennen
mv datei.txt /tmp/ # Datei in anderes Verzeichnis verschieben Linux hat keinen Papierkorb. Was du mit rm löschst, ist sofort und unwiderruflich weg. Sei besonders vorsichtig:
rm datei.txt # Einzelne Datei löschen -- keine Rückfrage!
rm -r ordner/ # Ordner mit allem Inhalt löschen
rm -i datei.txt # Sicher: fragt vor dem Löschen nach rm -rf / löscht das gesamte System -- führe diesen Befehl niemals aus. Tippe immer genau hin, was du löschen willst.
grep durchsucht Dateien nach Textmustern -- wie eine Suchfunktion, nur für das Terminal. find sucht nach Dateien und Ordnern im Dateisystem selbst.
grep "error" /var/log/syslog # Zeilen mit "error" finden
grep -i "error" /var/log/syslog # Groß-/Kleinschreibung ignorieren
grep -r "Port 22" /etc/ # Rekursiv in allen Dateien unter /etc/
grep -n "root" /etc/passwd # Mit Zeilennummer
grep -v "#" /etc/ssh/sshd_config # Zeilen OHNE Kommentare anzeigen find /etc -name "*.conf" # Alle .conf-Dateien unter /etc
find /var/log -name "*.log" -mtime -1 # Log-Dateien, geändert in den letzten 24h
find /home -size +100M # Dateien größer als 100 MB
find /tmp -type f -empty # Leere Dateien finden Konfigurationsdateien wie sshd_config enthalten oft mehr Kommentare als echte Einstellungen. Mit grep -v "^#" /etc/ssh/sshd_config | grep -v "^$" siehst du nur die aktiven Zeilen -- ohne Kommentare und Leerzeilen.
Die wirkliche Stärke der Shell zeigt sich, wenn du Befehle miteinander kombinierst. Mit dem Pipe-Zeichen | leitest du die Ausgabe eines Befehls direkt als Eingabe in den nächsten. Stell dir eine Fließbandproduktion vor: jeder Befehl macht einen Arbeitsschritt, dann gibt er das Ergebnis weiter.
# Lauschende Ports finden und nach SSH filtern
ss -tulpn | grep ":22"
# Wie viele Benutzer gibt es auf dem System?
cat /etc/passwd | wc -l
# Die 5 größten Dateien unter /var/log finden
du -ah /var/log | sort -rh | head -5
# Aktive Einstellungen aus sshd_config (ohne Kommentare und Leerzeilen)
grep -v "^#" /etc/ssh/sshd_config | grep -v "^$" ls /var/log > logdateien.txt # Ausgabe in Datei speichern (überschreibt)
echo "Neue Zeile" >> logdateien.txt # An bestehende Datei anhängen
find /etc -name "*.conf" 2>/dev/null # Fehlermeldungen verwerfen Jedes Programm hat drei Kanäle: stdin (Eingabe), stdout (normale Ausgabe) und stderr (Fehlermeldungen). Mit > leitest du stdout um, mit 2> stderr. Mit 2>/dev/null verwirfst du Fehlermeldungen -- sehr nützlich bei find.
Diese kleinen Tricks machen den Unterschied zwischen mühsamer Tipparbeit und flüssigem Arbeiten.
# Befehlshistorie
history # Letzte Befehle anzeigen
history | grep "ssh" # Nur SSH-Befehle aus der Historie
# In der Historie navigieren:
# Pfeiltaste hoch/runter = vorherige/nächste Befehle
# Ctrl+R = interaktive Rückwärtssuche in der Historie
# !! = letzten Befehl wiederholen
# !ssh = letzten Befehl, der mit "ssh" anfing, wiederholen # Temporärer Alias (gilt nur in dieser Sitzung)
alias ll="ls -lah"
alias frei="df -h"
# Permanente Aliase: in ~/.bashrc eintragen
echo 'alias ll="ls -lah"' >> ~/.bashrc
source ~/.bashrc # Änderungen sofort aktiv machen | Tastenkürzel | Aktion |
|---|---|
Tab | Befehl oder Pfad vervollständigen |
Tab Tab | Alle Möglichkeiten zur Vervollständigung anzeigen |
| Pfeiltaste oben/unten | Durch frühere Befehle blättern |
Ctrl+R | In der Befehlshistorie rückwärts suchen |
Ctrl+C | Laufenden Befehl abbrechen |
Ctrl+L | Terminal leeren (wie clear) |
Ctrl+A | Cursor an den Anfang der Zeile |
Ctrl+E | Cursor ans Ende der Zeile |
Wenn du eine bestimmte Information brauchst oder einen Befehl nicht verstehst, beschreibe dein Ziel: "Erkläre mir diesen Bash-Befehl Schritt für Schritt: du -ah /var/log | sort -rh | head -5. Was macht jeder Teil?" -- oder: "Wie finde ich unter Linux alle Dateien im Ordner /var/log, die größer als 50 MB sind und in den letzten 7 Tagen geändert wurden?"
Auf Servern gibt es keine Maus und keine grafischen Programme. Um eine Konfigurationsdatei zu bearbeiten, brauchst du einen Editor, der direkt im Terminal läuft. vim ist auf praktisch jedem Linux-System vorinstalliert -- auch auf minimalen Server-Installationen ohne Extras.
vim hat einen Ruf, kompliziert zu sein. Der Grund: Es gibt Modi. Du kannst nicht einfach lostippen. Aber sobald du die drei wichtigsten Modi verstehst, wirst du feststellen, dass vim extrem effizient ist -- wie ein Spezialwerkzeug, das sich zunächst fremd anfühlt, aber nach kurzer Übung nicht mehr wegzudenken ist.
Die drei Modi von vim
| Modus | Wozu | Wie aktivieren |
|---|---|---|
| Normal-Modus | Navigieren, Löschen, Kopieren -- der Standard-Modus beim Öffnen | Esc |
| Einfüge-Modus | Text eingeben und bearbeiten | i |
| Befehlsmodus | Speichern, Beenden, Suchen, Ersetzen | : (Doppelpunkt) |
vim datei.txt # Datei öffnen (oder neu erstellen)
# Du startest im Normal-Modus
i # → Einfüge-Modus: jetzt kannst du tippen
Esc # → zurück zum Normal-Modus (immer dein Sicherheitsnetz)
# Speichern und Beenden (aus dem Normal-Modus, dann : drücken)
:w # Speichern (write)
:q # Beenden (quit) -- nur wenn keine ungespeicherten Änderungen
:wq # Speichern und beenden
:q! # Beenden OHNE Speichern (Änderungen verwerfen) Du musst vim nicht meistern -- du musst es bedienen können. Merke dir diese eine Sequenz: i zum Tippen → Esc → :wq zum Speichern und Raus. Das reicht für 90 % der Fälle. Wenn du dich verirrt hast: Esc drücken, dann :q! -- raus ohne Speichern, kein Schaden angerichtet.
Nützliche vim-Befehle im Normal-Modus
| Taste | Aktion |
|---|---|
i | Einfüge-Modus (vor dem Cursor) |
a | Einfüge-Modus (nach dem Cursor) |
o | Neue Zeile darunter + Einfüge-Modus |
dd | Ganze Zeile löschen |
yy | Ganze Zeile kopieren |
p | Einfügen (nach dem Cursor) |
u | Rückgängig machen (Undo) |
gg | Zum Anfang der Datei springen |
G | Zum Ende der Datei springen |
/suchbegriff | Suchen -- n = nächster Treffer |
:set number | Zeilennummern einblenden |
:%s/alt/neu/g | Alle "alt" durch "neu" ersetzen |
Alternative: nano
Wenn vim im Moment zu viel ist, gibt es auch nano -- einen einfacheren Editor, der die Tastenkürzel am unteren Rand anzeigt. Ctrl+O speichert, Ctrl+X beendet. Trotzdem: Lerne vim. Manche Systembefehle wie visudo oder crontab -e öffnen automatisch vim -- dann musst du wissen, wie du rauskommst.
vim hat hunderte Befehle. Du musst sie nicht auswendig lernen. Wenn du etwas Bestimmtes tun willst: "Wie ersetze ich in vim in der gesamten Datei alle Vorkommen von 'localhost' durch '192.168.1.50'? Zeige mir den Befehl und erkläre die Syntax."
Du lernst die grundlegenden Navigationsbefehle, indem du dich durch das Dateisystem bewegst und einen Arbeitsordner für spätere Module anlegst.
pwd. Du solltest /home/deinname sehen. Schaue dir dann den Inhalt an: ls -la./var/log und liste den Inhalt sortiert nach Größe auf: cd /var/log && ls -lhS. Welche Log-Datei ist am größten?tail -15 syslog. Du bist bereits in /var/log, daher reicht der Dateiname ohne vollen Pfad.cd. Erstelle dort einen Übungsordner: mkdir ~/uebung. Wechsle hinein und erstelle drei leere Dateien: touch a.txt b.txt c.txt.a.txt nach a-kopie.txt: cp a.txt a-kopie.txt. Benenne b.txt um: mv b.txt notizen.txt. Prüfe das Ergebnis: ls -la.Lösungshinweise anzeigen
Nach dem SSH-Login zeigt pwd typischerweise /home/admin (dein Home-Verzeichnis). In /var/log ist syslog oft die größte Datei.
Im Ordner ~/uebung solltest du nach Schritt 5 vier Dateien sehen: a.txt, a-kopie.txt, notizen.txt und c.txt. Alle haben 0 Bytes, da touch leere Dateien erstellt.
Du kombinierst grep, find und Pipes, um gezielt Informationen aus dem System zu extrahieren -- genau das, was du im Server-Alltag regelmäßig brauchst.
grep "/bin/bash" /etc/passwd. Wie viele sind es?/etc/passwd: cat /etc/passwd | wc -l. Die meisten sind Systembenutzer ohne echte Login-Shell..conf-Dateien unter /etc und zähle sie: find /etc -name "*.conf" 2>/dev/null | wc -l. Das 2>/dev/null unterdrückt Fehlermeldungen für Ordner, auf die du keinen Zugriff hast.grep -v "^#" /etc/ssh/sshd_config | grep -v "^$"./var: sudo du -ah /var 2>/dev/null | sort -rh | head -5. Welche Datei belegt am meisten Platz?Lösungshinweise anzeigen
Auf einem frischen Ubuntu-Server haben typischerweise nur ein oder zwei Benutzer /bin/bash als Shell. Die meisten Einträge in /etc/passwd sind Systembenutzer mit /usr/sbin/nologin -- das ist normal und kein Problem.
Die aktiven SSH-Einstellungen sollten zehn bis zwanzig Zeilen ergeben -- deutlich weniger als die gesamte Datei, die meist über hundert Zeilen mit Kommentaren enthält.
Du übst vim, indem du eine Datei erstellst, Text eingibst, speicherst und wieder bearbeitest. Das ist genau der Ablauf, den du später bei echten Konfigurationsdateien brauchst.
vim ~/uebung/notizen.txt. Du startest im Normal-Modus -- du kannst noch nicht direkt tippen.i für den Einfüge-Modus. Unten siehst du -- INSERT --. Tippe drei Zeilen Text ein, z.B. deinen Servernamen, die IP-Adresse und ein kurzes Memo.Esc, um in den Normal-Modus zurückzukehren. Tippe dann :wq und bestätige mit Enter. Die Datei wird gespeichert und vim beendet sich.cat ~/uebung/notizen.txt. Stehen deine drei Zeilen drin?vim ~/uebung/notizen.txt. Navigiere mit den Pfeiltasten zur zweiten Zeile. Drücke dd im Normal-Modus, um die Zeile zu löschen. Speichere mit :wq.cat ~/uebung/notizen.txt: Ist die zweite Zeile wirklich weg? Wenn ja, hast du vim erfolgreich benutzt.Lösungshinweise anzeigen
Wenn du nach dem Öffnen von vim einfach lostippst und wirre Buchstaben oder Befehle erscheinen, bist du noch im Normal-Modus. Drücke Esc, dann :q! zum Beenden ohne Speichern -- und fang neu an.
Das -- INSERT -- am unteren Rand zeigt dir: du bist im Einfüge-Modus und kannst tippen. Wenn es fehlt, bist du im Normal-Modus.
Falls du dich mit :wq verspeichert hast (z.B. ein Tippfehler in der Datei): Öffne sie erneut mit vim, navigiere zur falschen Zeile und benutze u (Undo) oder dd (Zeile löschen).
Dokumentation mit man
Du musst keine Optionen auswendig lernen. Linux bringt sein Handbuch direkt ins Terminal -- man zeigt dir zu jedem Befehl alles, was du brauchst. In diesem Modul lernst du, das Handbuch zu lesen, schnell darin zu suchen und mit tldr noch schneller ans Ziel zu kommen.
Alle Befehle in diesem Modul gibst du auf dem Server ein -- du bist bereits per SSH eingeloggt. man und die anderen Werkzeuge laufen direkt in deiner Terminal-Sitzung auf dem Server.
Stell dir vor, du kaufst ein neues Werkzeug und es liegt ein vollständiges Reparaturhandbuch bei -- auf Knopfdruck abrufbar. Genau das ist man (kurz für manual). Jeder installierte Befehl bringt seine eigene Dokumentation mit, die du jederzeit aufrufen kannst.
Rufe die man-Page eines Befehls auf, indem du man gefolgt vom Befehlsnamen eingibst:
man ls # Handbuch für den Befehl ls
man grep # Handbuch für grep
man ssh # Handbuch für SSH Die man-Page öffnet sich im Pager less -- den du bereits aus Modul 03 kennst. Navigiere so:
| Taste | Aktion |
|---|---|
Leertaste / Page Down | Eine Seite vorblättern |
b / Page Up | Eine Seite zurückblättern |
/suchbegriff | Vorwärts suchen (z. B. /-i für die Option -i) |
n | Nächsten Treffer anspringen |
N | Vorherigen Treffer anspringen |
q | Beenden und zurück zum Terminal |
Man-Pages sind oft lang. Statt alles durchzulesen: Drücke / und tippe den Namen der Option, nach der du suchst -- z. B. /-r in man grep. Mit n springst du von Treffer zu Treffer.
Alle man-Pages folgen einem einheitlichen Schema. Sobald du das kennst, findest du dich in jeder man-Page sofort zurecht -- egal ob man ssh oder man tar:
| Abschnitt | Inhalt |
|---|---|
NAME | Befehlsname und eine kurze Beschreibung, was er tut |
SYNOPSIS | Syntax-Übersicht: welche Optionen und Argumente gibt es? |
DESCRIPTION | Ausführliche Beschreibung des Befehls |
OPTIONS | Alle verfügbaren Flags erklärt (-l, --help, ...) |
EXAMPLES | Praxisbeispiele -- nicht in jeder man-Page vorhanden |
FILES | Konfigurationsdateien, die der Befehl verwendet |
SEE ALSO | Verwandte Befehle und Handbücher |
Die SYNOPSIS sieht oft einschüchternd aus. Eckige Klammern [...] bedeuten: optional. Alles ohne Klammern ist Pflicht. ... bedeutet: kann mehrfach angegeben werden. Beispiel: ls [OPTION]... [DATEI]... -- alle Optionen und Dateinamen sind optional.
Das Linux-Handbuch ist in Sektionen aufgeteilt -- ähnlich wie Kapitel in einem Ordner. Manche Namen tauchen in mehreren Sektionen auf. passwd zum Beispiel gibt es als Befehl (Sektion 1) und als Konfigurationsdatei (Sektion 5):
| Sektion | Inhalt | Typisches Beispiel |
|---|---|---|
1 | Benutzerbefehle (täglich genutzt) | man 1 grep |
2 | Systemaufrufe (Kernel-Schnittstellen) | man 2 open |
3 | Bibliotheksfunktionen (Programmierung) | man 3 printf |
4 | Gerätedateien (/dev/...) | man 4 tty |
5 | Dateiformate und Konfigurationsdateien | man 5 passwd |
6 | Spiele und Bildschirmschoner | man 6 fortune |
7 | Übersichten und Konventionen | man 7 ip |
8 | Systembefehle (oft Root erforderlich) | man 8 iptables |
9 | Kernel-Routinen (nur für Entwickler) | man 9 kmalloc |
Ohne Sektionsnummer öffnet man immer die niedrigste passende Sektion. Mit einer Zahl davor wählst du gezielt:
man passwd # Öffnet Sektion 1 -- der Befehl zum Passwort ändern
man 5 passwd # Öffnet Sektion 5 -- Format der Datei /etc/passwd
man 5 sshd_config # Alle Optionen der SSH-Server-Konfiguration
man 5 fstab # Aufbau und Syntax von /etc/fstab Wenn du verstehen willst, welche Einstellungen eine Konfigurationsdatei hat, schau in Sektion 5 nach. man 5 sshd_config erklärt jede einzelne Option des SSH-Servers -- genauer als jeder Blog-Artikel.
Du weißt, was du tun willst -- aber nicht, wie der Befehl heißt? Kein Problem. apropos durchsucht alle man-Page-Beschreibungen nach deinem Stichwort. Das ist wie eine Volltextsuche im Handbuch.
apropos password # Alle Befehle, die mit Passwörtern zu tun haben
apropos network # Alle Befehle zum Thema Netzwerk
apropos disk # Alle Befehle rund um Festplatten
# man -k ist identisch mit apropos
man -k password # Gleiches Ergebnis wie apropos password
# whatis: nur eine kurze Einzeiler-Beschreibung
whatis grep # → grep (1) - print lines that match patterns
whatis passwd # Zeigt alle Sektionen, in denen passwd vorkommt apropos disk zeigt oft Dutzende Treffer. Kombiniere es mit grep aus Modul 03: apropos disk | grep -i format filtert die Ausgabe weiter. So findest du schnell, was du suchst.
Manchmal brauchst du keine vollständige man-Page. Für eine schnelle Optionsübersicht reicht --help oder das moderne Werkzeug tldr.
Fast jeder Befehl kennt --help oder -h -- das gibt eine kurze Übersicht aller Optionen aus, ohne den Pager zu öffnen:
ls --help
grep --help
tar --help # Kürzer als die man-Page, aber vollständig tldr geht noch einen Schritt weiter: Es zeigt nur die häufigsten Anwendungsfälle mit kopierbaren Beispielen. Erst installieren, dann nutzen:
sudo apt install tldr
tldr --update # Datenbank beim ersten Mal aktualisieren (braucht Internet)
tldr tar # Die häufigsten tar-Befehle auf einen Blick
tldr find # find-Beispiele -- deutlich übersichtlicher als man find
tldr ssh-keygen # Bekannt aus Modul 02 -- sieh dir die Kurzfassung an | Werkzeug | Wann benutzen | Ausgabe |
|---|---|---|
man befehl | Vollständige Dokumentation, alle Optionen | Viel Text, im Pager |
befehl --help | Schnelle Optionsübersicht | Direkt ins Terminal |
tldr befehl | Häufigste Anwendungsfälle mit Beispielen | Kompakt, farbig |
whatis befehl | Eine-Zeile-Beschreibung | Einzeiler |
apropos stichwort | Befehl suchen, Name unbekannt | Liste passender Befehle |
Man-Pages sind fast immer auf Englisch. Wenn du eine Option nicht verstehst, kopiere den entsprechenden Abschnitt und frag: "Übersetze und erkläre mir diesen Abschnitt der man-Page von sshd_config auf Deutsch. Was bedeuten die Optionen im Alltag eines Server-Administrators?"
Du lernst, man-Pages effizient zu nutzen -- ohne jeden Browser zu öffnen. Alle Schritte laufen auf dem Server.
man grep. Drücke / und suche nach -i. Welche Funktion hat die Option -i laut Beschreibung?man 5 sshd_config. Suche nach /PermitRootLogin. Welche Werte sind laut Handbuch erlaubt?man passwd und man 5 passwd -- öffne beide nacheinander. Was beschreibt jeweils die DESCRIPTION-Sektion?apropos disk. Welche Befehle findest du? Erkennst du einen, der aus Modul 03 bekannt ist?whatis ls und whatis passwd, was dir die Einzeiler-Beschreibung sagt. Siehst du bei passwd mehrere Sektionen?Lösungshinweise anzeigen
Die Option -i bei grep steht für "ignore case" -- Groß- und Kleinschreibung werden beim Suchen ignoriert.
PermitRootLogin in der sshd_config akzeptiert: yes, no, prohibit-password und forced-commands-only. Empfehlung: no.
man passwd (Sektion 1) beschreibt den Befehl zum Ändern des Passworts. man 5 passwd beschreibt das Dateiformat von /etc/passwd -- die Liste aller Benutzer auf dem System.
whatis passwd zeigt dir beide Einträge: passwd (1) und passwd (5) in einer Ausgabe.
Du installierst tldr und lernst, wann du welches Werkzeug einsetzt. Alle Schritte laufen auf dem Server.
sudo apt install tldr. Danach einmal die Datenbank aktualisieren: tldr --update. Das braucht eine Internetverbindung.man tar und dann tldr tar. Wie viele Zeilen hat jeweils die Ausgabe? Welche Variante ist für den schnellen Alltag besser?tldr find auf. Findest du direkt einen Befehl, den du sofort für eine Dateisuche kopieren könntest?tldr ssh-keygen -- den Befehl kennst du aus Modul 02. Stimmt die Kurzfassung mit dem überein, was du damals gemacht hast?Lösungshinweise anzeigen
man tar hat mehrere hundert Zeilen mit allen Optionen. tldr tar zeigt in etwa 20 Zeilen die häufigsten Anwendungsfälle mit kopierbaren Beispielen. Für den Alltag ist tldr deutlich schneller.
Falls tldr --update fehlschlägt, prüfe die Internetverbindung des Servers. Die Datenbank wird von GitHub heruntergeladen.
Die Kurzfassung von tldr ssh-keygen zeigt genau den Befehl ssh-keygen -t ed25519, den du in Modul 02 verwendet hast.
Ein typischer Alltag: Du weißt, was du tun willst, aber nicht den genauen Befehl. Mit apropos findest du ihn in Sekunden.
apropos disk | grep free. Welche Ergebnisse bekommst du?apropos compress. Welche Befehle kennst du schon, welche sind neu?man befehl als auch -- falls vorhanden -- tldr befehl auf. Nutze /EXAMPLES in der man-Page, um direkt zum Beispiel-Abschnitt zu springen.Lösungshinweise anzeigen
apropos disk | grep free liefert in der Regel df (1) -- "disk free". df -h zeigt den freien Platz in lesbaren Einheiten (KB, MB, GB).
apropos compress zeigt Werkzeuge wie gzip, bzip2, xz und zip. In Modul 03 hast du bereits tar kennengelernt, das oft mit gzip kombiniert wird.
In der man-Page direkt zum EXAMPLES-Abschnitt springen: /EXAMPLES eingeben und Enter drücken.
Netzwerk-Konfiguration
Ein Server ohne feste IP-Adresse ist wie ein Handwerksbetrieb ohne Straßenadresse -- niemand findet ihn zuverlässig. In diesem Modul lernst du, wie du den Netzwerkstatus prüfst, eine statische IP einrichtest und DNS konfigurierst.
Alle Befehle und Konfigurationsdateien in diesem Modul befinden sich auf dem Server. Du bist bereits per SSH eingeloggt (aus Modul 02). Es gibt hier keinen Wechsel zwischen deinem Rechner und dem Server -- alles läuft dort.
Aus Modul 01 weißt du, was IP-Adressen und Netzwerkinterfaces sind. Jetzt lernst du, wie du sie auf deinem Server anzeigst und liest. Ein Netzwerk-Interface ist der Netzwerkanschluss des Servers -- vergleichbar mit der Netzwerkdose an der Wand. Jedes Interface bekommt eine IP-Adresse zugewiesen.
Zeige alle vorhandenen Netzwerk-Interfaces an:
ip link show
# Typische Ausgabe:
# 1: lo: <LOOPBACK,UP> -- Loopback (127.0.0.1, nur intern)
# 2: eth0: <BROADCAST,UP> -- Ethernet-Anschluss (Kabel)
# 3: ens18: <BROADCAST,UP> -- Ethernet in VMs (z.B. Proxmox) Mit UP in der Ausgabe ist das Interface aktiv. Zeige dann IP-Adressen und Routing-Tabelle:
ip addr show # Alle Interfaces mit IP-Adressen
ip addr show eth0 # Nur ein bestimmtes Interface
ip -4 addr show # Nur IPv4-Adressen (übersichtlicher)
ip route show # Routing-Tabelle -- Gateway finden Das Gateway steht in der Routing-Tabelle nach default via -- das ist die IP-Adresse deines Routers.
| Interface-Name | Bedeutung |
|---|---|
lo | Loopback -- internes Interface (127.0.0.1), niemals umbenennen |
eth0 | Klassischer Ethernet-Name (ältere Systeme) |
enp3s0 | Neuerer Ethernet-Name (Predictable Network Interface Names) |
ens18 | Ethernet in virtuellen Maschinen (z.B. Proxmox, VMware) |
wlan0 / wlp2s0 | WLAN-Interface (auf Servern selten) |
Standardmäßig beziehen die meisten Systeme ihre IP-Adresse per DHCP automatisch vom Router. Das ist für Laptops praktisch -- für Server ein Problem. Stell dir vor, du hast in deiner Werkstatt eine Telefonnummer, die sich jeden Morgen ändert: Kein Kunde erreicht dich zuverlässig.
Bei Servern ist es genauso: SSH-Verbindungen, Dienste und andere Geräte im Netzwerk verlassen sich auf eine gleichbleibende IP-Adresse. Deshalb gilt für Server: immer statische IP einrichten.
Viele Router bieten DHCP-Reservierung an: Der Router vergibt immer dieselbe IP an eine bestimmte MAC-Adresse des Servers. Das ist einfacher einzurichten, aber du bist vom Router abhängig. Fällt der Router aus oder wird zurückgesetzt, verliert der Server seine feste IP. Für Produktionsserver ist die statische IP direkt auf dem Server die sicherere Wahl.
Auf Ubuntu-Servern wird das Netzwerk mit Netplan konfiguriert. Netplan ist wie ein Konfigurationszettel: Du trägst ein, welche IP der Server haben soll, wo das Gateway ist und welche DNS-Server er nutzen soll -- und Netplan sorgt dafür, dass das System sich daran hält.
Die Konfigurationsdateien liegen unter /etc/netplan/ und sind im YAML-Format geschrieben.
Aktuelle Konfiguration lesen
Zeige, welche Netplan-Dateien vorhanden sind und lies ihren Inhalt:
ls /etc/netplan/ # Welche Dateien gibt es?
cat /etc/netplan/*.yaml # Inhalt der Konfiguration anzeigen DHCP-Konfiguration (Standard-Ausgangslage)
So sieht eine typische DHCP-Konfiguration aus -- der Server bezieht seine IP automatisch vom Router:
network:
version: 2
ethernets:
eth0: # Interface-Name -- bei dir vielleicht ens18 oder enp3s0
dhcp4: true Statische IP einrichten
Bearbeite die Netplan-Datei mit vim (aus Modul 03). Ersetze den DHCP-Block durch eine statische Konfiguration:
network:
version: 2
ethernets:
eth0:
dhcp4: false
addresses:
- 192.168.1.50/24 # Deine gewünschte IP + Subnetzmaske
routes:
- to: default
via: 192.168.1.1 # IP-Adresse deines Routers (Gateway)
nameservers:
addresses:
- 1.1.1.1 # Cloudflare DNS
- 9.9.9.9 # Quad9 DNS (Backup) Nach dem Bearbeiten die Konfiguration testen und anwenden:
# Syntax prüfen (zeigt Fehler, ändert noch nichts)
sudo netplan generate
# Änderungen testen -- 120 Sekunden Zeit zum Bestätigen
sudo netplan try
# Bei Verbindung OK: Enter drücken
# Bei Problemen: einfach 120 Sek. warten, automatischer Rollback
# Direkt anwenden (nur wenn du dir sicher bist)
sudo netplan apply Nutze beim ersten Testen immer sudo netplan try, niemals direkt sudo netplan apply. Bei try hast du 120 Sekunden, um die Verbindung zu bestätigen. Wenn du dich durch einen Tippfehler (falsche IP, falsches Gateway) aussperrst, werden die Änderungen automatisch zurückgesetzt und du kannst dich wieder per SSH verbinden. Bei apply ohne Absicherung brauchst du im Fehlerfall physischen Zugang zum Server.
YAML (das Format der Netplan-Dateien) verwendet Einrückung zur Struktur. Dabei müssen es immer Leerzeichen sein -- keine Tabs. Viele Editoren fügen beim Drücken der Tab-Taste automatisch einen Tab ein, nicht Leerzeichen. Prüfe in vim mit :set list, ob du Tabs siehst (dann als ^I dargestellt). Falsche Einrückung führt zu Syntaxfehlern, die schwer zu finden sind.
Nutze immer sudo netplan try -- damit siehst du Syntaxfehler sofort, bevor sie Schaden anrichten.
| Netplan-Feld | Bedeutung | Beispiel |
|---|---|---|
dhcp4: false | DHCP für IPv4 deaktivieren | -- |
addresses | Statische IP mit Subnetzmaske (CIDR) | 192.168.1.50/24 |
routes - to: default via: | Standard-Gateway (dein Router) | 192.168.1.1 |
nameservers.addresses | DNS-Server-Liste | 1.1.1.1, 9.9.9.9 |
Netplan-YAML ist fehleranfällig -- ein falsch gesetztes Leerzeichen reicht für einen Syntaxfehler. Beschreib dein Setup und lass dir die fertige Datei generieren: "Erstelle mir eine Netplan-Konfiguration für Ubuntu 24.04: Interface ens18, statische IP 10.0.1.100/24, Gateway 10.0.1.1, DNS 1.1.1.1 und 8.8.8.8. Zeige die komplette Datei." Kopiere das Ergebnis direkt in den Editor.
DNS ist das Telefonbuch des Internets: Es übersetzt Hostnamen wie google.com in IP-Adressen. Wenn DNS nicht funktioniert, können keine Pakete installiert werden, keine Webseiten aufgerufen werden und keine Verbindungen zu externen Diensten aufgebaut werden -- obwohl die Netzwerkverbindung selbst funktioniert.
Prüfe, welchen DNS-Server dein System aktuell verwendet, und teste die DNS-Auflösung:
# Aktuell verwendete DNS-Server anzeigen (systemd-resolved)
resolvectl status
# Klassische Datei (auf Ubuntu oft ein Symlink, nicht direkt bearbeiten)
cat /etc/resolv.conf
# DNS-Auflösung testen
dig +short google.com # Zeigt nur die IP-Adresse
ping -c 2 google.com # Testet Verbindung + DNS gleichzeitig Auf modernen Ubuntu-Systemen ist /etc/resolv.conf ein Symlink, der von systemd-resolved automatisch verwaltet wird. Manuelle Änderungen werden beim nächsten Neustart überschrieben. Konfiguriere DNS-Server stattdessen in der Netplan-Datei unter nameservers.addresses -- dann bleibt die Einstellung dauerhaft erhalten.
Der Hostname ist der Name deines Servers -- wie das Firmenschild über der Tür. Er erscheint im Terminal-Prompt und wird bei der Kommunikation im Netzwerk verwendet. Setze einen sinnvollen Hostnamen, damit du bei mehreren Servern den Überblick behältst.
Hostname anzeigen und dauerhaft ändern:
# Aktuellen Hostnamen anzeigen
hostname
hostnamectl # Ausführlichere Informationen (Betriebssystem, Kernel)
# Hostnamen dauerhaft ändern
sudo hostnamectl set-hostname webserver01
# Wirkt sofort, im Prompt erst nach neuer SSH-Sitzung sichtbar /etc/hosts -- lokale Namensauflösung
Die Datei /etc/hosts ist das lokale Telefonbuch des Servers: Sie ordnet IP-Adressen Hostnamen zu, ohne einen DNS-Server zu befragen. Einträge hier haben Vorrang vor DNS. Das ist nützlich für Server im lokalen Netzwerk, die du unter einem Namen statt einer IP-Adresse ansprechen willst.
Bearbeite /etc/hosts mit vim (aus Modul 03):
sudo vim /etc/hosts
# Standardinhalt (nicht löschen):
127.0.0.1 localhost
127.0.1.1 webserver01 # Eigener Hostname -- hier anpassen
# Eigene Einträge für Geräte im Netzwerk hinzufügen:
192.168.1.1 router
192.168.1.50 webserver01
192.168.1.51 dbserver01
192.168.1.52 backupserver Danach kannst du statt IP-Adressen die Namen verwenden:
ping -c 2 dbserver01 # statt: ping 192.168.1.51
ssh admin@dbserver01 # statt: ssh admin@192.168.1.51 Auf Desktop-Installationen und manchen Server-Varianten ist statt Netplan der NetworkManager aktiv. Du erkennst das an: ls /etc/netplan/ gibt nichts aus, aber systemctl status NetworkManager zeigt einen laufenden Dienst.
Die wichtigsten nmcli-Befehle zum Prüfen und Konfigurieren:
# Alle Verbindungen anzeigen
nmcli connection show
# Details einer Verbindung anzeigen
nmcli connection show "Wired connection 1"
# Statische IP setzen
sudo nmcli connection modify "Wired connection 1" \
ipv4.method manual \
ipv4.addresses 192.168.1.50/24 \
ipv4.gateway 192.168.1.1 \
ipv4.dns "1.1.1.1,9.9.9.9"
# Verbindung neu starten (Änderungen aktivieren)
sudo nmcli connection down "Wired connection 1"
sudo nmcli connection up "Wired connection 1"
# Zurück auf DHCP
sudo nmcli connection modify "Wired connection 1" ipv4.method auto Ubuntu Server 20.04+ verwendet standardmäßig Netplan. Ubuntu Desktop und manche Cloud-Images nutzen den NetworkManager. Prüfe mit ls /etc/netplan/ und systemctl status NetworkManager, welches System aktiv ist. In diesem Kurs arbeiten wir vorwiegend mit Netplan.
Wenn das Netzwerk nicht funktioniert, gehe diese Schritte der Reihe nach durch -- von unten nach oben im Netzwerk-Stack. Stell dir vor, du prüfst eine Wasserleitung: erst den Haupthahn, dann die Rohre, dann den Wasserdruck am Auslass.
Netzwerk-Diagnose von der untersten Ebene aufwärts:
# Schritt 1: Ist das Interface überhaupt aktiv?
ip link show
# "UP" muss in der Ausgabe stehen
# Schritt 2: Hat das Interface eine IP-Adresse?
ip addr show
# Nach "inet" suchen -- z.B. "inet 192.168.1.50/24"
# Schritt 3: Ist das Gateway (der Router) erreichbar?
ping -c 3 192.168.1.1
# IP deines Routers hier eintragen
# Schritt 4: Funktioniert die Internet-Verbindung (ohne DNS)?
ping -c 3 8.8.8.8
# Schritt 5: Funktioniert DNS-Auflösung?
ping -c 3 google.com | Schritt | Befehl | Was du prüfst | Fehler zeigt auf |
|---|---|---|---|
| 1 | ip link show | Interface aktiv? | Kabel, Hardware, Treiber |
| 2 | ip addr show | IP-Adresse vorhanden? | DHCP oder statische Konfig |
| 3 | ping 192.168.1.1 | Gateway erreichbar? | IP-Konfig, Subnetz, Gateway |
| 4 | ping 8.8.8.8 | Internet erreichbar? | Router, ISP, Firewall |
| 5 | ping google.com | DNS funktioniert? | DNS-Server-Eintrag |
Schritt 4 funktioniert, aber Schritt 5 nicht? DNS ist das Problem -- prüfe die nameservers-Einträge in Netplan. Schritt 3 funktioniert nicht? Die IP-Adresse oder das Gateway ist falsch konfiguriert.
Wenn dein Netzwerk korrekt konfiguriert ist, aber bestimmte Dienste trotzdem nicht erreichbar sind, kann die Firewall des Servers der Grund sein. Ports müssen in der Firewall explizit freigegeben werden. Das lernst du in Modul 14 (Firewall-Administration).
Wenn die Diagnose einen Fehler aufzeigt, aber du nicht weiterkommst, zeig der KI die Ausgabe der Befehle: "Mein Ubuntu-Server hat nach der Netplan-Änderung kein Internet mehr. Schritt 4 (ping 8.8.8.8) funktioniert, aber Schritt 5 (ping google.com) nicht. Hier ist meine Netplan-Datei und die Ausgabe von resolvectl status: [einfügen]. Was ist falsch?"
Du liest die aktuelle Netzwerkkonfiguration deines Servers und verstehst, wie er ans Netzwerk angebunden ist. Das ist der erste Schritt, bevor du irgendetwas änderst.
ip link show. Notiere den Namen deines Haupt-Interfaces (das erste, das nicht lo ist).ip addr show INTERFACE-NAME. Welche IP steht nach inet? Ist es eine private Adresse (192.168.x.x oder 10.x.x.x)?ip route show. Welche IP steht nach default via? Das ist dein Gateway (Router).ls /etc/netplan/. Gibt es eine YAML-Datei? Zeige den Inhalt: cat /etc/netplan/*.yaml.resolvectl status. Suche die Zeile "DNS Servers" -- welche IP steht dort?Lösungshinweise anzeigen
Das Haupt-Interface heißt je nach System eth0, enp3s0 oder ens18 (in VMs häufig). Die IP-Adresse erscheint in ip addr show nach inet mit der Subnetzmaske, z.B. inet 192.168.1.100/24.
Falls /etc/netplan/ leer ist oder nicht existiert, nutzt dein System möglicherweise den NetworkManager. Prüfe mit systemctl status NetworkManager.
Du richtest eine feste IP-Adresse auf deinem Server ein. Danach ist er immer unter derselben Adresse erreichbar -- egal wie oft du ihn neu startest.
sudo cp /etc/netplan/*.yaml /tmp/netplan-backup.yaml. Bei Problemen kannst du darauf zurückgreifen.sudo vim /etc/netplan/01-netcfg.yaml. Setze dhcp4: false und trage eine statische IP (aus deinem Heimnetz, z.B. 192.168.1.50/24), das Gateway (dein Router) und DNS-Server (1.1.1.1) ein. Orientiere dich am Beispiel aus dem Abschnitt "Statische IP einrichten".sudo netplan try. Du hast 120 Sekunden, um die SSH-Verbindung zu testen und mit Enter zu bestätigen. Klappt die Verbindung noch? Dann bestätigen.ip addr show. Stimmt die Adresse mit dem überein, was du in die Netplan-Datei eingetragen hast?ping -c 4 google.com. Kommen Antworten zurück?Lösungshinweise anzeigen
Achte auf korrekte YAML-Einrückung: Immer 2 Leerzeichen pro Ebene, keine Tabs. Die IP-Adresse muss im selben Subnetz wie dein Gateway liegen -- bei Gateway 192.168.1.1 also eine Adresse aus dem Bereich 192.168.1.2 bis 192.168.1.254 mit der Maske /24.
Falls du dich aussperrst: Bei netplan try werden Änderungen nach 120 Sekunden automatisch zurückgesetzt. Du kannst dich dann neu verbinden und die Konfiguration korrigieren.
Du gibst deinem Server einen sinnvollen Namen und trägst andere Geräte im Netzwerk in die lokale Hostnamen-Datei ein, damit du sie beim Namen statt per IP-Adresse ansprechen kannst.
hostnamectl. Wie heißt dein Server gerade?sudo hostnamectl set-hostname webserver01. Wähle einen Namen ohne Leerzeichen und Sonderzeichen./etc/hosts mit vim: sudo vim /etc/hosts. Ändere den alten Hostnamen in der Zeile mit 127.0.1.1 auf den neuen Namen.192.168.1.1 router (passe die IP an deinen Router an). Speichere und schließe vim.ping -c 2 router. Wird der Name aufgelöst und kommt eine Antwort?Lösungshinweise anzeigen
Der neue Hostname ist sofort aktiv, erscheint aber erst im Terminal-Prompt, wenn du eine neue SSH-Sitzung öffnest (alte Sitzung beenden, neu anmelden).
Einträge in /etc/hosts haben Vorrang vor DNS. Du könntest dort auch andere Server im Netzwerk eintragen -- dann sparst du dir das Tippen von IP-Adressen bei SSH-Verbindungen oder Ping-Tests.
Du lernst, Netzwerkprobleme systematisch einzugrenzen. Auch wenn gerade alles funktioniert, ist es wichtig, die Diagnose-Schritte zu kennen -- sie helfen dir, wenn später mal etwas nicht läuft.
ip link show. Steht bei deinem Haupt-Interface UP? Was bedeutet der Status DOWN?ip addr show. Welche IP hat das Interface? Passt sie zum konfigurierten Subnetz?ip route show. Ping dann das Gateway: ping -c 3 GATEWAY-IP. Kommen Antworten zurück?ping -c 2 8.8.8.8 aus (nur IP, kein DNS), dann ping -c 2 google.com (braucht DNS). Was unterscheidet die Ausgaben?resolvectl status. Suche den Abschnitt deines Interfaces und den Eintrag "DNS Servers".Lösungshinweise anzeigen
Wenn ping 8.8.8.8 funktioniert, aber ping google.com nicht: DNS ist das Problem. Entweder kein DNS-Server konfiguriert, oder der konfigurierte DNS-Server ist nicht erreichbar.
Wenn ping GATEWAY-IP nicht funktioniert: IP-Adresse oder Subnetz ist falsch. Prüfe, ob deine statische IP und das Gateway im selben Subnetz liegen.
APT -- Paketverwaltung
Unter Linux installierst du Software nicht per Download aus dem Internet, sondern über den Paketmanager. APT ist dein Ersatzteilkatalog: Du sagst, was du brauchst, und APT holt es aus dem richtigen Lager -- geprüft, aktuell, mit allem Zubehör.
Alle Befehle in diesem Modul gibst du auf dem Server ein -- du bist bereits per SSH eingeloggt. Es gibt keinen Wechsel zwischen lokalem Rechner und Server.
Stell dir vor, du bestellst Ersatzteile für eine Werkstatt. Du rufst beim Lieferanten an, sagst „Ich brauche eine Zündkerze für einen Golf 7", und der Lieferant schickt dir das passende Teil -- inklusive der Dichtung, die dazugehört. Genau so funktioniert APT.
APT (Advanced Package Tool) ist der Paketmanager für Debian und Ubuntu. Er:
- Holt Software aus geprüften Repositories (Paketquellen -- wie ein Teile-Lager im Internet)
- Löst Abhängigkeiten automatisch auf -- Programm A braucht Bibliothek B, APT installiert beides
- Aktualisiert alles mit einem einzigen Befehl
- Entfernt Software sauber und vollständig
Unter Windows lädst du .exe-Dateien von beliebigen Webseiten herunter -- mit dem Risiko, Schadsoftware zu erwischen. APT holt Software aus offiziellen, kryptografisch signierten Quellen. Das ist sicherer und bequemer.
Bevor du etwas installierst, musst du APT sagen: „Schau nach, was es Neues gibt." Das ist der Unterschied zwischen update und upgrade -- ein häufiger Stolperstein am Anfang.
apt update lädt die aktuellen Paketlisten herunter -- es wird noch nichts installiert. Erst apt upgrade installiert die neueren Versionen.
sudo apt update
# APT schaut nach, was es Neues gibt -- noch nichts wird installiert
# Am Ende steht: "X packages can be upgraded." sudo apt upgrade
# Alle veralteten Pakete auf die neueste Version bringen
# APT fragt nach Bestätigung -- mit "j" oder "Enter" bestätigen
# Oder beides in einem Zug (typische Kombination):
sudo apt update && sudo apt upgrade apt update schaut nur in den Katalog -- wie ein Blick in die Preisliste. apt upgrade bestellt tatsächlich -- wie das Abschicken der Bestellung. Immer erst update, dann upgrade. Und auf einem Produktions-Server: teste Updates vorher in einer Testumgebung.
Die drei Kernaufgaben eines Paketmanagers: installieren, finden, loswerden.
# Ein Paket installieren
sudo apt install htop
# Mehrere Pakete auf einmal
sudo apt install htop curl wget tree
# Ohne Rückfrage (praktisch in Skripten)
sudo apt install -y htop # Nach einem Paket suchen (Stichwortsuche)
apt search webserver
# Details zu einem Paket anzeigen (Version, Größe, Beschreibung)
apt show nginx
# Alle installierten Pakete auflisten
apt list --installed
# Prüfen ob ein bestimmtes Paket installiert ist
apt list --installed | grep nginx # Paket entfernen -- Konfigurationsdateien bleiben erhalten
sudo apt remove htop
# Paket vollständig entfernen -- auch Konfiguration weg
sudo apt purge htop
# Nicht mehr benötigte Abhängigkeiten aufräumen
sudo apt autoremove apt remove ist wie Demontage: Die Teile weg, aber die Schrauben noch da. apt purge ist die komplette Entsorgung -- inklusive aller Einstellungen. Wenn du ein Paket mit neuer Konfiguration neu installieren willst, nimm immer zuerst purge.
Hier sind alle Befehle, die du in diesem Modul kennengelernt hast -- als schnelle Referenz zum Nachschlagen.
| Befehl | Was er tut |
|---|---|
sudo apt update | Paketlisten aktualisieren (kein Download von Software) |
sudo apt upgrade | Alle installierten Pakete auf den neuesten Stand bringen |
sudo apt install PAKET | Ein Paket installieren |
sudo apt remove PAKET | Paket entfernen, Konfiguration bleibt |
sudo apt purge PAKET | Paket vollständig entfernen inkl. Konfiguration |
sudo apt autoremove | Verwaiste Abhängigkeiten aufräumen |
apt search STICHWORT | In der Paketliste suchen |
apt show PAKET | Details zu einem Paket anzeigen |
apt list --installed | Alle installierten Pakete auflisten |
APT-Fehlermeldungen sind oft kryptisch: „unmet dependencies", „dpkg was interrupted", „unable to acquire the dpkg frontend lock". Kopiere die komplette Fehlermeldung und frag: „Ich bekomme diesen Fehler bei 'sudo apt install nginx' auf Ubuntu 24.04: [Fehlermeldung einfügen]. Was ist das Problem und wie behebe ich es Schritt für Schritt?"
APT weiß, wo es Software holen soll, durch die Paketquellen. Die offizielle Liste liegt in /etc/apt/sources.list und im Ordner /etc/apt/sources.list.d/. Für die meisten Pakete reichen die Standard-Quellen von Ubuntu.
Aktuelle Paketquellen anzeigen:
# Klassische Paketquellen-Datei anzeigen
cat /etc/apt/sources.list
# Zusätzlich eingebundene Quellen anzeigen
ls /etc/apt/sources.list.d/ Manchmal brauchst du Software, die nicht in den offiziellen Quellen ist -- zum Beispiel Docker oder eine aktuelle Node.js-Version. Dann fügst du ein externes Repository hinzu. Das geht immer nach dem gleichen Muster:
- GPG-Schlüssel herunterladen (zur Verifizierung -- damit du sicher weißt, wer das Paket gebaut hat)
- Repository-Adresse in
sources.list.d/eintragen sudo apt updateausführen- Software installieren
Fremde Repositories können manipulierte Pakete enthalten -- du gibst damit jemandem das Recht, Software auf deinem Server zu installieren. Nutze nur Repositories von bekannten Herstellern (Docker, Microsoft, PostgreSQL, NodeSource). Verwende immer den GPG-Schlüssel zur Verifizierung. Zufällige Anleitungen aus dem Internet ohne GPG-Schlüssel: Finger weg.
Jeder Hersteller hat seinen eigenen Weg, ein Repository einzubinden. Lass dir den aktuellen, korrekten Befehl generieren: „Wie binde ich das offizielle Docker-Repository auf Ubuntu 24.04 ein? Zeig mir die aktuellen Befehle mit GPG-Schlüssel." -- Das ist besser als eine veraltete Anleitung zu kopieren.
Sicherheitslücken werden regelmäßig entdeckt. Ein Server, der wochenlang ohne Patches läuft, ist ein offenes Scheunentor. Mit unattended-upgrades werden Sicherheitsupdates automatisch eingespielt -- ohne dass du daran denken musst.
# Paket installieren
sudo apt install unattended-upgrades
# Aktivieren -- wähle "Ja" in der Dialogbox
sudo dpkg-reconfigure -plow unattended-upgrades
# Status prüfen: Läuft der Dienst?
systemctl status unattended-upgrades Die Konfiguration findest du in /etc/apt/apt.conf.d/50unattended-upgrades. Standardmäßig werden nur Sicherheitsupdates automatisch installiert -- keine neuen Features, die etwas kaputt machen könnten.
Richte unattended-upgrades auf jedem neu aufgesetzten Server als erste Maßnahme ein. Bekannte Sicherheitslücken werden dadurch automatisch geschlossen -- selbst wenn du wochenlang nicht einloggst.
APT ist das komfortable Oberwerkzeug. Darunter arbeitet dpkg (Debian Package) -- das eigentliche Installationsprogramm. Du brauchst dpkg direkt nur in zwei Fällen: wenn du eine .deb-Datei manuell installieren willst, oder wenn du Informationen über installierte Pakete abfragst.
# Lokale .deb-Datei installieren
sudo dpkg -i paket.deb
# Falls Abhängigkeiten fehlen, danach ausführen:
sudo apt install -f
# Prüfen ob und welche Version eines Pakets installiert ist
dpkg -s nginx
# Alle Dateien anzeigen, die ein Paket installiert hat
dpkg -L nginx
# Welches Paket stellt eine bestimmte Datei bereit?
dpkg -S /usr/bin/vim Nutze apt für alles, was mit dem Internet zu tun hat: installieren, aktualisieren, suchen. Nutze dpkg nur für lokale .deb-Dateien oder zum Abfragen von Informationen über installierte Pakete.
Du lernst die grundlegenden APT-Befehle, die du auf jedem Linux-Server täglich brauchst. Alle Schritte laufen auf dem Server.
sudo apt update. Wie viele Pakete können aktualisiert werden? Die Zahl steht am Ende der Ausgabe.tree: sudo apt install tree. Bestätige mit "j". Teste es danach mit: tree /etc/apthtop an, bevor du es installierst: apt show htop. Wie groß ist es? Den Wert findest du bei "Installed-Size".htop: sudo apt install htop. Starte es mit htop und schau dir CPU und RAM-Auslastung an. Beende es mit q.tree vollständig: sudo apt purge tree. Räume danach auf: sudo apt autoremove.htop noch installiert ist: apt list --installed | grep htop. Du solltest eine Zeile mit Version und Architektur sehen.Lösungshinweise anzeigen
tree /etc/apt zeigt die Verzeichnisstruktur von /etc/apt als Baumdiagramm -- sehr praktisch, um sich schnell einen Überblick zu verschaffen.
htop ist ein interaktiver Prozess-Monitor. Oben siehst du CPU-Balken für jeden Kern und den RAM-Verbrauch. Mit den Pfeiltasten kannst du durch die Prozessliste scrollen, mit F9 einen Prozess beenden.
apt list --installed | grep htop gibt eine Zeile aus, wenn htop installiert ist, z.B.: htop/noble,now 3.3.0-4 amd64 [installiert]
Du bringst das System auf den neuesten Stand und richtest automatische Sicherheitsupdates ein -- eine Pflichtmaßnahme auf jedem produktiven Server.
sudo apt update && sudo apt upgrade. Bestätige mit "j". Warte, bis der Vorgang abgeschlossen ist.sudo apt install unattended-upgrades.sudo dpkg-reconfigure -plow unattended-upgrades. In der Dialogbox "Ja" auswählen und Enter drücken.systemctl status unattended-upgrades. Du solltest "active (running)" oder "active (waiting)" sehen.grep -v "//" /etc/apt/apt.conf.d/50unattended-upgrades | grep -v "^$" | head -20. Welche Update-Quellen sind aktiviert?Lösungshinweise anzeigen
Nach dem Upgrade sollte APT melden: „0 upgraded, 0 newly installed" -- alle Pakete sind jetzt auf dem neuesten Stand.
Der Dienst unattended-upgrades zeigt "active (waiting)" wenn er auf den nächsten geplanten Ausführungszeitpunkt wartet -- das ist normal und korrekt.
In der Konfigurationsdatei sind standardmäßig nur Sicherheitsupdates aktiviert (${distro_id}:${distro_codename}-security). Das ist die sichere Wahl: Kritische Lücken werden geschlossen, aber keine neuen Features installiert, die etwas kaputt machen könnten.
Du übst die Suche nach Paketen und lernst, wie du mit dpkg Informationen über installierte Software abrufst.
apt search monitor. Die Liste ist lang -- du kannst die Ausgabe einschränken: apt search monitor | grep -i "system monitor"curl an: apt show curl. Notiere dir Version und die Installationsgröße (Zeile "Installed-Size").curl installiert ist: dpkg -s curl. Was steht unter "Status"?curl installiert hat: dpkg -L curl. Wo liegt die eigentliche Programmdatei?/usr/bin/curl bereitstellt: dpkg -S /usr/bin/curl.Lösungshinweise anzeigen
dpkg -s curl zeigt "Status: install ok installed" -- das bedeutet, das Paket ist korrekt installiert.
dpkg -L curl listet alle installierten Dateien: die Programmdatei liegt unter /usr/bin/curl, die Handbuchseite unter /usr/share/man/man1/curl.1.gz.
dpkg -S /usr/bin/curl gibt zurück: curl: /usr/bin/curl -- das Paket heißt also curl.
Grafischer Remote Desktop
Manchmal reicht die Kommandozeile nicht aus. In diesem Modul installierst du eine Desktop-Umgebung auf deinem Server und verbindest dich grafisch per RDP -- als wärst du direkt davor.
In diesem Modul arbeitest du an zwei Orten: Auf dem Server installierst und konfigurierst du alles. Von deinem eigenen Rechner aus verbindest du dich dann grafisch per RDP-Client. Wir markieren das durch diese Zonen:
RDP-Client starten, Verbindung herstellen -- auf deinem Laptop oder PC.
Desktop-Umgebung und xrdp installieren -- per SSH eingeloggt.
Die meisten Linux-Server laufen ohne Desktop -- und das ist gut so. Ein schlanker Server ohne GUI verbraucht weniger Arbeitsspeicher und hat eine kleinere Angriffsfläche. Trotzdem gibt es Situationen, in denen eine grafische Oberfläche sinnvoll ist:
- Grafische Verwaltungs-Tools -- manche Software bietet eine GUI, die deutlich komfortabler ist als die Kommandozeile
- Webbrowser auf dem Server -- um interne Dienste zu testen, die nur auf
localhosterreichbar sind - Entwicklung und Tests -- IDEs, grafische Editoren oder Test-Anwendungen auf einem dedizierten Server
- Linux-Einstieg -- für Anfänger kann ein Desktop das Eingewöhnen erleichtern
Auf einem Server, der Webseiten, Datenbanken oder andere Live-Dienste betreibt, solltest du keine Desktop-Umgebung installieren. Sie frisst RAM und CPU und vergrößert die Angriffsfläche. Nutze Desktop-Umgebungen auf Test- und Entwicklungsservern oder in virtuellen Maschinen.
Es gibt zwei gängige Protokolle für grafischen Remote-Zugriff. Stell dir RDP wie ein Telefongespräch vor -- jeder hat seinen eigenen Hörer. VNC hingegen ist wie ein Blick über die Schulter -- du siehst denselben Bildschirm wie der Nutzer am Server.
| Eigenschaft | RDP (xrdp) | VNC (TigerVNC) |
|---|---|---|
| Port | 3389 | 5901 (erste Sitzung) |
| Verschlüsselung | Eingebaut (TLS) | Keine -- SSH-Tunnel nötig! |
| Windows-Client | Bereits eingebaut (mstsc) | Separater Client nötig |
| Mehrere Nutzer | Ja -- jeder bekommt eigene Sitzung | Meist eine Sitzung gleichzeitig |
| Einsatzbereich | Normale Desktop-Nutzung | Fernwartung, bestehende Sitzung zeigen |
xrdp ist die bessere Wahl für den Einstieg: einfacher einzurichten, Windows-Client bereits installiert, Verschlüsselung eingebaut. VNC brauchst du nur, wenn du die laufende Desktop-Sitzung eines anderen Nutzers sehen willst.
Für Server empfiehlt sich XFCE -- leichtgewichtig, schnell und trotzdem komfortabel. Es braucht nur rund 300 MB RAM und läuft flüssig auch auf schwächerer Hardware.
Paketliste aktualisieren und XFCE installieren -- du kennst dieses Prinzip bereits aus Modul 06:
sudo apt update
sudo apt install xfce4 xfce4-goodies
# xfce4 = Basis-Desktop
# xfce4-goodies = nützliche Extras: Terminal, Dateimanager, Screenshot-Tool Die Installation lädt 200--400 MB herunter und dauert einige Minuten. Danach ist XFCE auf dem Server bereit -- du kannst es aber noch nicht sehen, weil du per SSH ohne Bildschirm verbunden bist. Dafür brauchen wir xrdp.
xrdp ist ein Open-Source-RDP-Server. Er nimmt Verbindungen auf Port 3389 entgegen und startet für jeden Nutzer eine eigene XFCE-Sitzung.
sudo apt install xrdp
# Dienst starten und beim Boot automatisch starten:
sudo systemctl enable --now xrdp
# Status prüfen -- sollte "active (running)" zeigen:
sudo systemctl status xrdp Jetzt teile xrdp mit, welchen Desktop es starten soll. Dafür schreibst du eine kurze Steuerdatei in dein Home-Verzeichnis:
echo "startxfce4" > ~/.xsession
# Prüfen, ob es geschrieben wurde:
cat ~/.xsession
# Ausgabe sollte sein: startxfce4 Prüfe, ob xrdp auf Port 3389 lauscht -- das kennst du schon aus Modul 01:
ss -tulpn | grep 3389
# Erwartete Ausgabe: tcp LISTEN ... 0.0.0.0:3389 ... xrdp Jetzt wechselst du auf deinen eigenen Rechner. Der Server wartet auf Port 3389 -- du rufst ihn mit einem RDP-Client an.
Windows: Remotedesktopverbindung
Windows hat den RDP-Client bereits eingebaut -- du brauchst nichts zu installieren:
- Drücke
Win + R, tippemstscund drücke Enter - Gib die IP-Adresse deines Servers ein (z.B.
192.168.1.50) und klicke Verbinden - Bestätige die Zertifikatswarnung beim ersten Mal mit Ja
- Melde dich mit deinem Linux-Benutzernamen und Passwort an
- Der XFCE-Desktop erscheint im Fenster
Linux: Remmina
Unter Linux nimmst du Remmina -- einen vielseitigen Remote-Desktop-Client, der RDP, VNC und mehr unterstützt:
sudo apt install remmina remmina-plugin-rdp Starte Remmina aus dem Anwendungsmenü oder mit remmina im Terminal. Wähle dort Neues Verbindungsprofil, setze das Protokoll auf RDP, trage die Server-IP ein und verbinde dich.
Du kennst die IP-Adresse deines Servers bereits aus Modul 02. Alternativ findest du sie in der Router-Oberfläche (meist 192.168.x.x) oder -- falls du direkten Zugang zum Server hast -- mit ip addr show direkt am Server-Terminal.
Ein offener Remote-Desktop-Port ist ein beliebtes Angriffsziel. Schränke den Zugriff deshalb auf dein lokales Netz ein. Details zur Firewall-Verwaltung lernst du in Modul 14 -- hier die wichtigste Regel schon vorab:
# Aktuellen Firewall-Status prüfen:
sudo ufw status
# RDP nur aus dem lokalen Netz (192.168.1.0/24) erlauben:
sudo ufw allow from 192.168.1.0/24 to any port 3389
# Ergebnis prüfen:
sudo ufw status numbered Passe den IP-Bereich (192.168.1.0/24) an dein Netz an. Bei Unsicherheit: ip addr show zeigt deine Server-IP -- der Netzbereich ist die IP ohne die letzte Zahl plus /24.
Öffne Port 3389 niemals für das gesamte Internet. RDP-Ports sind ein Hauptziel für automatisierte Brute-Force-Angriffe. Wenn du von außen auf deinen Desktop zugreifen musst, nutze einen SSH-Tunnel (aus Modul 02): ssh -L 3389:localhost:3389 user@server-ip -- dann bleibt der Port geschlossen und du verbindest dich zu localhost:3389.
VNC ist nützlich, wenn du die laufende Desktop-Sitzung eines anderen Nutzers sehen willst -- zum Beispiel für Fernwartung oder Support. Anders als RDP zeigt VNC immer denselben Bildschirm, egal wer sich verbindet.
sudo apt install tigervnc-standalone-server
# VNC-Passwort setzen (wird beim Verbinden abgefragt):
vncpasswd
# VNC-Server starten (Display :1 = Port 5901):
vncserver :1
# Laufende Sitzungen prüfen:
vncserver -list VNC überträgt standardmäßig keine verschlüsselten Daten. Nutze VNC niemals direkt über das Netzwerk ohne Schutz. Leite die Verbindung immer durch einen SSH-Tunnel -- der Aufbau funktioniert genauso wie in Modul 02 für Datenbankdienste beschrieben.
ssh -L 5901:localhost:5901 user@server-ip
# Dann VNC-Client mit localhost:5901 verbinden -- NICHT mit der Server-IP Diese Fehler begegnen dir häufig bei der Einrichtung:
| Problem | Ursache | Lösung |
|---|---|---|
| Schwarzer Bildschirm nach Login | ~/.xsession fehlt oder falsch | echo "startxfce4" > ~/.xsession, dann neu verbinden |
| Login-Schleife (immer wieder Login-Bildschirm) | Fehlerhafter Desktop-Start oder falsches Passwort | Logs prüfen: journalctl -u xrdp -n 30 |
| Verbindung wird abgelehnt | xrdp läuft nicht oder Firewall blockiert | sudo systemctl status xrdp und sudo ufw status prüfen |
| Desktop extrem langsam | Desktop-Umgebung zu schwer | GNOME durch XFCE ersetzen |
| Tastaturlayout falsch | Locale-Einstellungen im Desktop | In XFCE: Einstellungen → Tastatur → Layout |
Wenn nach dem xrdp-Login nur ein schwarzer Bildschirm erscheint, steckt die Ursache fast immer in den Logs. Führe auf dem Server journalctl -u xrdp --no-pager -n 30 aus, kopiere die Ausgabe und frag eine KI: „Nach dem xrdp-Login sehe ich nur einen schwarzen Bildschirm. Hier sind meine Logs -- was ist das Problem und wie behebe ich es?"
Du installierst eine grafische Desktop-Umgebung auf deinem Server und verbindest dich zum ersten Mal per RDP. Am Ende siehst du einen echten XFCE-Desktop im Fenster.
sudo apt update && sudo apt install xfce4 xfce4-goodies xrdp. Das dauert ein paar Minuten.sudo systemctl enable --now xrdp. Prüfe den Status: sudo systemctl status xrdp -- du solltest active (running) sehen.echo "startxfce4" > ~/.xsession. Prüfe: cat ~/.xsession -- die Ausgabe muss startxfce4 sein.ss -tulpn | grep 3389. Du solltest eine Zeile mit :3389 und xrdp sehen.sudo ufw allow from 192.168.1.0/24 to any port 3389 (IP-Bereich anpassen).Win + R, tippen mstsc und geben die Server-IP ein. Linux-Nutzer starten Remmina und wählen RDP als Protokoll. Melde dich mit deinem Linux-Benutzernamen und Passwort an.Lösungshinweise anzeigen
systemctl status xrdp muss active (running) zeigen. Bei ss -tulpn | grep 3389 muss eine Zeile mit 0.0.0.0:3389 und dem Prozess xrdp erscheinen.
Wenn nach dem Login ein schwarzer Bildschirm erscheint: Prüfe mit cat ~/.xsession ob die Datei startxfce4 enthält. Falls nicht, den Befehl aus Schritt 3 wiederholen und die RDP-Sitzung neu starten.
Du richtest TigerVNC ein und verbindest dich sicher über einen SSH-Tunnel. Damit übst du die Tunnel-Technik aus Modul 02 in einem neuen Kontext.
sudo apt install tigervnc-standalone-servervncpasswd. Das Passwort wird beim Verbinden abgefragt. Das View-only-Passwort kannst du verneinen.vncserver :1. Prüfe mit vncserver -list, ob er läuft.ss -tulpn | grep 5901. Du solltest eine Zeile sehen, die 127.0.0.1:5901 enthält.ssh -L 5901:localhost:5901 user@server-ip. Das Terminal bleibt geöffnet -- der Tunnel läuft so lange wie diese Verbindung.localhost:5901 -- nicht zur Server-IP. Gib das VNC-Passwort ein.Lösungshinweise anzeigen
Der SSH-Tunnel leitet Port 5901 deines lokalen Rechners verschlüsselt an Port 5901 des Servers weiter. Im VNC-Client verbindest du dich daher mit localhost:5901 -- der Tunnel kümmert sich um den Rest.
VNC-Server stoppen geht mit vncserver -kill :1 auf dem Server. Mit vncserver -list siehst du alle laufenden Sitzungen.
Statt Port 3389 in der Firewall zu öffnen, leitest du xrdp durch einen SSH-Tunnel. So bleibt der Port von außen unsichtbar -- eine Profi-Methode für maximale Sicherheit.
sudo systemctl status xrdp. Notiere die Server-IP für den nächsten Schritt.ssh -L 3389:localhost:3389 user@server-ip. Das Terminal bleibt geöffnet -- schließe es nicht.mstsc, Linux: Remmina) und verbinde dich mit localhost als Zieladresse -- nicht mit der Server-IP.Lösungshinweise anzeigen
Der SSH-Tunnel leitet localhost:3389 auf deinem Rechner an localhost:3389 auf dem Server weiter -- vollständig verschlüsselt. Der RDP-Client weiß nicht, dass er gerade durch einen SSH-Tunnel schaut.
Mit dieser Methode kannst du die ufw-Regel für Port 3389 komplett entfernen und trotzdem per RDP arbeiten. Das ist deutlich sicherer als ein offener Port im Netz.
XFCE speichert viele Einstellungen in Konfigurationsdateien unter ~/.config/xfce4/. Wenn du XFCE auf mehreren Servern gleich einrichten willst oder Einstellungen per Skript setzen möchtest, frag eine KI: „Ich möchte XFCE auf Ubuntu so konfigurieren, dass das Panel oben ist und das Tastaturlayout auf Deutsch gestellt ist -- welche Konfigurationsdateien muss ich ändern und wie?"
Nutzer & Gruppenverwaltung
Linux ist ein Mehrbenutzersystem – jeder Nutzer hat eigene Rechte, eigene Dateien und eigene Grenzen. In diesem Modul lernst du, wie du Benutzer anlegst, Gruppen verwaltest und mit sudo sicher Root-Rechte vergibst.
Alle Befehle in diesem Modul gibst du auf dem Server ein – du bist bereits per SSH eingeloggt (siehe Modul 02).
Stell dir einen Server vor wie ein Bürogebäude: Jeder Mitarbeiter hat einen eigenen Schlüssel, einen eigenen Schreibtisch und Zugang zu bestimmten Räumen. Du würdest nicht jedem den Generalschlüssel geben – und genauso solltest du auf einem Server nicht jedem Root-Rechte einräumen.
Linux unterscheidet drei Arten von Benutzern:
| Typ | UID-Bereich | Beispiel | Zweck |
|---|---|---|---|
| Root | 0 | root | Allmächtiger Systemadministrator |
| Systembenutzer | 1–999 | www-data, sshd | Dienste isoliert betreiben |
| Normale Benutzer | 1000+ | max, anna | Echte Menschen, die sich anmelden |
Der Benutzer root darf alles – auch das System unwiderruflich zerstören. Arbeite immer als normaler Benutzer und nutze sudo nur für einzelne Befehle, die Root-Rechte brauchen. Ein versehentliches rm -rf / als Root löscht das gesamte System ohne Rückfrage.
Linux speichert Benutzer-Informationen in drei Textdateien. Du kannst sie lesen (und bei Bedarf bearbeiten), aber für die meisten Aufgaben nutzt du die passenden Befehle.
/etc/passwd – Benutzerliste
Jeder Benutzer hat hier eine Zeile. Trotz des Namens stehen hier keine Passwörter (die sind in /etc/shadow).
cat /etc/passwd
# Beispielzeile:
# max:x:1001:1001:Max Mustermann:/home/max:/bin/bash Aufbau einer Zeile in /etc/passwd – alle sieben Felder sind durch Doppelpunkt getrennt:
| Feld | Beispielwert | Bedeutung |
|---|---|---|
| 1 | max | Benutzername |
| 2 | x | Passwort-Platzhalter (x = in /etc/shadow) |
| 3 | 1001 | User-ID (UID) |
| 4 | 1001 | Primäre Gruppen-ID (GID) |
| 5 | Max Mustermann | Kommentar (voller Name) |
| 6 | /home/max | Home-Verzeichnis |
| 7 | /bin/bash | Login-Shell |
/etc/shadow – Passwörter (verschlüsselt)
Diese Datei ist nur für Root lesbar. Sie enthält die verschlüsselten (gehashten) Passwörter. Das $6$ am Anfang des Hashes bedeutet SHA-512 – ein modernes, sicheres Verfahren.
Wenn in /etc/shadow statt einem Hash ein ! oder * steht, ist das Konto gesperrt – der Benutzer kann sich nicht mit Passwort anmelden. Systembenutzer wie www-data haben bewusst kein Passwort.
/etc/group – Gruppenliste
Jede Gruppe hat eine Zeile. Die Mitglieder stehen am Ende, durch Komma getrennt:
| Feld | Beispielwert | Bedeutung |
|---|---|---|
| 1 | entwickler | Gruppenname |
| 2 | x | Passwort-Platzhalter (selten genutzt) |
| 3 | 1005 | Gruppen-ID (GID) |
| 4 | max,anna | Mitglieder (Komma-getrennt) |
Neuen Benutzer erstellen
Auf Debian/Ubuntu gibt es zwei Werkzeuge – adduser und useradd. Der Unterschied ist wichtig:
adduser ist ein benutzerfreundliches Wrapper-Skript: Es fragt interaktiv nach Passwort und Namen, legt automatisch das Home-Verzeichnis an und setzt sinnvolle Standardwerte. useradd ist das eigentliche Low-Level-Tool – es macht nur genau das, was du als Optionen mitgibst. Für den Alltag: nimm adduser.
# Empfohlener Weg: adduser (interaktiv, erstellt Home-Verzeichnis)
sudo adduser max
# → Fragt nach Passwort, vollem Namen usw.
# Alternativer Weg: useradd (low-level, mehr Kontrolle)
sudo useradd -m -s /bin/bash -c "Max Mustermann" max
# -m → Home-Verzeichnis erstellen (/home/max)
# -s → Login-Shell festlegen
# -c → Kommentar (voller Name)
# Passwort setzen (bei useradd manuell nötig):
sudo passwd max Benutzer anzeigen und prüfen
# Aktuellen Benutzer anzeigen:
whoami
# Alle Informationen über einen Benutzer:
id max
# Ausgabe: uid=1001(max) gid=1001(max) groups=1001(max),27(sudo)
# Alle normalen Benutzer auflisten (UID ab 1000):
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd
# Wer ist gerade eingeloggt?
who
w # ausführlicher: zeigt auch, was jeder Nutzer gerade tut Benutzer ändern
# Shell ändern:
sudo usermod -s /bin/zsh max
# Kommentar (voller Name) ändern:
sudo usermod -c "Max M. (Admin)" max
# Konto sperren (Login verhindern):
sudo usermod -L max
# Konto entsperren:
sudo usermod -U max Benutzer löschen
sudo userdel -r max löscht das komplette Home-Verzeichnis des Benutzers – inklusive aller Dateien und E-Mails. Es gibt kein Zurück. Prüfe vorher, ob wichtige Daten vorhanden sind: ls -la /home/max/. Erstelle im Zweifel vorher eine Sicherungskopie.
# Benutzer löschen (Home-Verzeichnis bleibt erhalten):
sudo userdel max
# Benutzer löschen MIT Home-Verzeichnis (Achtung: unwiderruflich!):
sudo userdel -r max
# Auf Debian/Ubuntu: benutzerfreundliche Alternative
sudo deluser --remove-home max Gruppen bündeln Rechte. Statt jedem Benutzer einzeln Zugriff auf einen Ordner zu geben, fügst du alle relevanten Benutzer einer Gruppe hinzu und gibst der Gruppe den Zugriff. Das ist wie ein Generalschlüssel für eine bestimmte Abteilung – alle Mitglieder kommen rein, der Rest nicht.
Primäre vs. sekundäre Gruppen
Jeder Benutzer hat genau eine primäre Gruppe (die Gruppe, der neue Dateien gehören) und kann beliebig viele sekundäre Gruppen haben (für zusätzliche Rechte).
# Neue Gruppe erstellen:
sudo groupadd entwickler
# Benutzer einer sekundären Gruppe hinzufügen:
sudo usermod -aG entwickler max
# -a → append (wichtig! Ohne -a werden alle anderen Gruppen entfernt!)
# -G → sekundäre Gruppen
# Mehrere Gruppen auf einmal hinzufügen:
sudo usermod -aG entwickler,docker,www-data max
# Gruppen eines Benutzers anzeigen:
groups max
# Ausgabe: max : max entwickler docker
# Alle Mitglieder einer Gruppe anzeigen:
getent group entwickler
# Gruppe löschen:
sudo groupdel entwickler sudo usermod -G entwickler max (ohne -a) setzt die Gruppenmitgliedschaft auf nur entwickler – alle anderen sekundären Gruppen (z.B. sudo!) werden entfernt. Max könnte sich danach nicht mehr als Admin einloggen. Immer -aG verwenden!
sudo (Superuser Do) erlaubt normalen Benutzern, einzelne Befehle mit Root-Rechten auszuführen – ohne das Root-Passwort zu kennen. Wer sudo nutzen darf, wird über die Datei /etc/sudoers und die Gruppe sudo gesteuert.
su vs. sudo – der Unterschied
| Befehl | Was er tut | Wann nutzen |
|---|---|---|
sudo befehl | Führt einen Befehl als Root aus, dann zurück zum normalen Benutzer | Normalfall – immer bevorzugen |
sudo -i | Öffnet eine vollständige Root-Shell (bleibt Root bis exit) | Nur wenn viele Root-Befehle nötig sind |
su - benutzer | Wechselt komplett zu einem anderen Benutzer (braucht dessen Passwort) | Zum Testen anderer Konten |
sudo-Zugang einrichten
# Einfachster Weg: Benutzer zur Gruppe 'sudo' hinzufügen
sudo usermod -aG sudo max
# Prüfen ob es geklappt hat:
groups max
# Ausgabe: max : max sudo
# Hinweis: Benutzer muss sich ab- und wieder anmelden,
# damit die neue Gruppenmitgliedschaft wirkt. /etc/sudoers sicher bearbeiten
Die Datei /etc/sudoers steuert, wer was mit sudo darf. Bearbeite sie niemals direkt mit einem Texteditor – nutze immer visudo, das die Syntax vor dem Speichern prüft:
sudo visudo
# Wichtige Zeilen in der Datei:
# root ALL=(ALL:ALL) ALL → Root darf alles
# %sudo ALL=(ALL:ALL) ALL → Gruppe 'sudo' darf alles
# Einzelnen Benutzer einschränken (nur apt erlauben):
# max ALL=(ALL) /usr/bin/apt
# sudo ohne Passwort (NUR für Automatisierung, nicht für normale Nutzer!):
# deploy ALL=(ALL) NOPASSWD: ALL Wenn du /etc/sudoers mit einem normalen Editor bearbeitest und einen Syntaxfehler einbaust, kann sich niemand mehr per sudo anmelden – auch du nicht. visudo prüft die Syntax vor dem Speichern und verhindert dieses Problem.
Wenn du regelmäßig neue Benutzer anlegen musst (z.B. für neue Mitarbeiter), frag eine KI: „Schreibe mir ein Bash-Skript, das einen neuen Benutzer anlegt, ihn zur Gruppe 'entwickler' hinzufügt, ein temporäres Passwort setzt und erzwingt, dass er es beim ersten Login ändert."
Sichere Passwörter sind die erste Verteidigungslinie. Mit chage (change age) kannst du einstellen, wie oft Benutzer ihr Passwort ändern müssen und wann Konten ablaufen:
# Passwort-Infos eines Benutzers anzeigen:
sudo chage -l max
# Passwort muss alle 90 Tage geändert werden:
sudo chage -M 90 max
# Mindestens 7 Tage zwischen Passwortänderungen:
sudo chage -m 7 max
# Benutzer muss beim nächsten Login das Passwort ändern:
sudo chage -d 0 max | Befehl | Funktion |
|---|---|
adduser NAME | Neuen Benutzer interaktiv anlegen (Debian/Ubuntu) |
useradd -m -s /bin/bash NAME | Benutzer low-level anlegen |
passwd NAME | Passwort setzen oder ändern |
usermod -aG GRUPPE NAME | Benutzer zu Gruppe hinzufügen |
usermod -L NAME | Konto sperren |
userdel NAME | Benutzer löschen (Home bleibt) |
userdel -r NAME | Benutzer + Home-Verzeichnis löschen |
groupadd GRUPPE | Neue Gruppe anlegen |
groupdel GRUPPE | Gruppe löschen |
groups NAME | Gruppen eines Benutzers anzeigen |
id NAME | UID, GID und alle Gruppen anzeigen |
getent group GRUPPE | Alle Mitglieder einer Gruppe anzeigen |
chage -l NAME | Passwort-Ablauf-Infos anzeigen |
visudo | /etc/sudoers sicher bearbeiten |
Erstelle eine Projektstruktur mit Benutzern und Gruppen – wie in einem echten Team mit Zugangsbeschränkungen.
sudo groupadd projektsudo adduser alice und danach sudo adduser bob. Vergib jeweils ein Passwort.sudo usermod -aG projekt alice und sudo usermod -aG projekt bobgroups alice und getent group projektid alice und id bobLösungshinweise anzeigen
Bei groups alice sollte projekt in der Liste erscheinen. Bei getent group projekt siehst du eine Zeile wie projekt:x:XXXX:alice,bob.
id alice zeigt UID, primäre GID und alle sekundären Gruppen. Achte darauf, dass projekt unter den Gruppen aufgeführt ist.
Gib einem Benutzer sudo-Rechte und überprüfe durch einen Vergleich mit einem Benutzer ohne sudo-Rechte, was den Unterschied macht.
sudo usermod -aG sudo alicesu - alice (Alices Passwort eingeben)sudo whoami – die Ausgabe sollte root seinexitsu - bob. Teste sudo whoami – was passiert?Lösungshinweise anzeigen
Alice kann sudo nutzen, weil sie in der Gruppe sudo ist. Bob bekommt die Meldung: bob is not in the sudoers file. This incident will be reported.
Die „This incident will be reported"-Meldung ist kein Scherz: Fehlgeschlagene sudo-Versuche werden in /var/log/auth.log protokolliert. Du kannst das prüfen mit: sudo grep sudo /var/log/auth.log | tail -5
Lerne die Konfigurationsdateien kennen, die hinter der Benutzerverwaltung stecken – und beobachte, wer was lesen darf.
grep alice /etc/passwd. Identifiziere UID, GID, Home-Verzeichnis und Shell.cat /etc/shadow. Was passiert? Dann mit Root-Rechten: sudo cat /etc/shadow | grep alicesudo chage -l alicesudo chage -d 0 alicesu - alice – du wirst sofort zur Passwortänderung aufgefordert.Lösungshinweise anzeigen
In /etc/passwd siehst du eine Zeile wie alice:x:1002:1002:Alice:/home/alice:/bin/bash. Das x bedeutet, dass das Passwort in /etc/shadow steht.
cat /etc/shadow ohne sudo gibt Permission denied – nur Root darf diese Datei lesen. Das ist ein wichtiger Sicherheitsmechanismus, der verhindert, dass normale Benutzer die Passwort-Hashes anderer Nutzer lesen können.
Wenn ein Benutzer „Permission denied" bekommt oder sich nicht einloggen kann, prüfe seine Konfiguration und frag eine KI: „Der Benutzer 'max' kann sich nicht per SSH einloggen. Hier ist seine Zeile aus /etc/passwd und die Ausgabe von 'id max' und 'sudo chage -l max'. Was könnte das Problem sein?"
Aufbau von Linux
Linux ist kein Geheimnis – es folgt einem klaren Bauplan. In diesem Modul lernst du, wie das System aufgebaut ist: vom Einschalten bis zum Login, vom Kernel bis zur letzten Konfigurationsdatei. Dieses Wissen brauchst du für alle weiteren Module.
In diesem Modul arbeitest du ausschließlich auf dem Server – du bist per SSH eingeloggt (wie in Modul 02 gelernt). Alle Befehle führst du im Server-Terminal aus:
Alle Befehle in diesem Modul laufen auf dem Server – du bist bereits per SSH eingeloggt.
Linux besteht aus mehreren Schichten, die zusammenarbeiten. Eine gute Analogie: Stell dir ein Auto vor.
- Der Kernel ist der Motor – du siehst ihn nicht direkt, aber alles hängt von ihm ab. Er verwaltet Hardware, Arbeitsspeicher und Prozesse.
- Die Shell (z.B. Bash) ist das Armaturenbrett und das Lenkrad – die Schnittstelle, über die du das System bedienst. Du gibst Befehle ein, die Shell gibt sie an den Kernel weiter.
- Die Distribution (z.B. Ubuntu oder Debian) ist die Automarke – sie kombiniert Kernel, Shell, Paketmanager und vorinstallierte Software zu einem fertigen Paket.
| Schicht | Beispiel | Aufgabe |
|---|---|---|
| Kernel | Linux 6.1 | Hardware steuern, Ressourcen verwalten |
| Shell | Bash, Zsh, Fish | Befehle entgegennehmen und ausführen |
| Paketmanager | apt (Debian/Ubuntu) | Software installieren und aktualisieren |
| Distribution | Ubuntu 24.04, Debian 12 | Alles zusammenpacken und pflegen |
Mit diesen Befehlen siehst du, welchen Kernel und welche Distribution dein Server nutzt:
# Kernel-Version anzeigen:
uname -r
# Ausgabe z.B.: 6.1.0-18-amd64
# Welche Shell nutzt du gerade?
echo $SHELL
# Ausgabe z.B.: /bin/bash
# Welche Distribution und Version?
cat /etc/os-release
lsb_release -a Der Linux-Kernel lädt nur die Treiber (Module), die er braucht – wie ein Werkzeugkasten, aus dem du nur das herausnimmst, was du gerade brauchst. Mit lsmod siehst du alle geladenen Module. Das sind typischerweise 60–100 Einträge: Dateisystem-Treiber, Netzwerkkarte, USB-Controller und mehr.
Wenn du deinen Server einschaltest, läuft in wenigen Sekunden eine feste Kette von Ereignissen ab. Stell dir das wie den Motorstart eines Autos vor: Zündschlüssel dreht → Anlasser springt an → Motor läuft → Bordelektronik wird aktiv. Beim Linux-Server sieht die Kette so aus:
| Phase | Was passiert | Ca. Dauer |
|---|---|---|
| 1. BIOS / UEFI | Hardware-Selbsttest (POST), Bootmedium suchen | 1–5 Sek. |
| 2. GRUB (Bootloader) | Kernel-Datei laden, Boot-Menü anzeigen | 1–3 Sek. |
| 3. Kernel | Hardware erkennen, Treiber laden, Root-Dateisystem einhängen | 2–5 Sek. |
| 4. initramfs | Temporäres Mini-System für frühe Treiber und Entschlüsselung | 1–2 Sek. |
| 5. systemd (PID 1) | Alle Dienste starten, Netzwerk hochfahren, Login bereitstellen | 3–15 Sek. |
BIOS vs. UEFI: Ältere Rechner nutzen das BIOS, modernere das UEFI. Der praktische Unterschied: UEFI unterstützt Festplatten über 2 TB und GPT-Partitionstabellen. Für neue Server immer UEFI verwenden.
# BIOS oder UEFI? (Verzeichnis vorhanden = UEFI)
ls /sys/firmware/efi/ && echo "UEFI" || echo "BIOS"
# Gesamte Bootzeit anzeigen:
systemd-analyze
# Ausgabe z.B.: Startup finished in 2.5s (kernel) + 8.3s (userspace) = 10.8s
# Die langsamsten Dienste beim Boot:
systemd-analyze blame | head -10
# Boot-Logs des aktuellen Starts:
journalctl -b --no-pager | tail -30 Auf Linux gibt es keine Laufwerksbuchstaben wie C: oder D:. Stattdessen hängt alles an einem einzigen Wurzelverzeichnis: /. Diese Struktur heißt FHS (Filesystem Hierarchy Standard) und ist auf allen Linux-Distributionen gleich aufgebaut.
Stell dir das vor wie einen großen Aktenschrank: Jede Schublade hat ein festes Thema – und alle wissen, wo was zu finden ist.
| Verzeichnis | Inhalt | Analogie |
|---|---|---|
/ | Wurzelverzeichnis – Ausgangspunkt für alles | Der gesamte Aktenschrank |
/home | Benutzerverzeichnisse (/home/anna, /home/bob) | Persönliche Schubladen der Mitarbeiter |
/etc | Konfigurationsdateien aller Programme | Ordner mit Einstellungen und Regeln |
/var | Variable Daten: Logs, Datenbanken, Mail-Warteschlangen | Posteingang und Logbücher |
/tmp | Temporäre Dateien – werden beim Neustart gelöscht | Schmierpapier auf dem Schreibtisch |
/usr | Programme, Bibliotheken, Dokumentation | Der Werkzeugkasten |
/bin, /sbin | Grundlegende Systembefehle (heute oft Symlinks auf /usr/bin) | Werkzeuge, die immer griffbereit sein müssen |
/boot | Kernel und Bootloader-Dateien | Der Zündschlüssel |
/dev | Gerätedateien: Festplatten (/dev/sda), USB, etc. | Anschlüsse an der Rückwand |
/proc, /sys | Virtuelle Dateisysteme mit Live-Infos über Kernel und Hardware | Dashboard mit Live-Anzeigen |
/mnt, /media | Einhängepunkte für externe Datenträger und Netzlaufwerke | Parkplätze für USB-Sticks |
/opt | Software, die nicht vom Paketmanager kommt (z.B. kommerzielle Tools) | Sonder-Werkzeuge außerhalb des Standards |
# Die erste Verzeichnisebene anzeigen:
ls /
# Was liegt in /etc?
ls /etc/ | head -20
# Wie viel Platz verbraucht jedes Verzeichnis?
sudo du -sh /* 2>/dev/null | sort -hr | head
# Hostnamen lesen:
cat /etc/hostname /etc enthält statische Konfiguration – Dateien, die du als Admin bearbeitest (sshd_config, fstab, hosts). /var enthält laufend wachsende Daten – Logs unter /var/log/, Datenbanken unter /var/lib/. Diese Unterscheidung hilft dir beim Backup: /etc regelmäßig sichern, /var/log rotiert sich selbst.
Jedes Programm, das auf dem Server läuft, ist ein Prozess. Jeder Prozess bekommt eine eindeutige Nummer: die PID (Process ID). systemd ist immer PID 1 – der erste Prozess nach dem Kernel. Alles andere ist ein Kind- oder Enkelprozess davon.
# Alle laufenden Prozesse anzeigen:
ps aux
# USER PID %CPU %MEM COMMAND
# root 1 0.0 0.5 /sbin/init (= systemd)
# www-da 812 0.2 1.3 nginx: master process
# Nach CPU-Auslastung sortiert, Top 10:
ps aux --sort=-%cpu | head -10
# Interaktive Prozessübersicht (beenden mit 'q'):
top
# Noch besser – falls installiert:
htop
# PID eines bestimmten Programms finden:
pgrep nginx
pidof sshd Prozesse beenden – Signale
Du kannst einem Prozess ein Signal schicken – ähnlich wie ein Handzeichen an einen Kollegen. Das häufigste Signal ist SIGTERM: „Bitte beende dich geordnet." Wenn das nicht hilft, kommt SIGKILL: „Sofort aufhören, egal was gerade passiert."
| Signal | Nummer | Bedeutung | Befehl |
|---|---|---|---|
| SIGTERM | 15 | Geordnet beenden (Standard) | kill PID |
| SIGKILL | 9 | Sofort abwürgen (kein Aufräumen möglich) | kill -9 PID |
| SIGHUP | 1 | Konfiguration neu laden | kill -1 PID |
| SIGSTOP | 19 | Prozess anhalten (pausieren) | kill -19 PID |
# Prozess geordnet beenden (SIGTERM, Standard):
kill 1234
# Ersetze 1234 durch die echte PID aus ps aux
# Prozess sofort abwürgen (SIGKILL, letzter Ausweg):
kill -9 1234
# Alle Prozesse eines Namens beenden:
pkill firefox
killall python3 SIGKILL (kill -9) gibt dem Prozess keine Chance, offene Dateien zu schließen oder Daten zu speichern. Das kann zu korrupten Dateien führen. Versuche immer zuerst kill PID (SIGTERM) und warte einige Sekunden. Erst wenn der Prozess danach noch läuft, kommt SIGKILL.
systemd ist der erste Prozess, der nach dem Kernel startet (PID 1). Er verwaltet alle Dienste (Daemons) auf deinem Server – von SSH bis Nginx. Die Grundeinheit von systemd ist die Unit. Die wichtigste Unit-Art ist der Service (z.B. sshd.service, nginx.service).
# Status eines Dienstes anzeigen:
systemctl status ssh
# Zeigt: läuft/gestoppt, letzte Logs, PID
# Dienst starten / stoppen / neustarten:
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
# Nur Konfiguration neu laden (ohne Neustart):
sudo systemctl reload nginx
# Dienst beim Boot automatisch starten:
sudo systemctl enable nginx
# Autostart wieder deaktivieren:
sudo systemctl disable nginx
# Alle laufenden Dienste auflisten:
systemctl list-units --type=service --state=running Runlevels vs. systemd-Targets
Frühere Linux-Systeme nutzten Runlevels (Zahlen 0–6). systemd ersetzt sie durch Targets – aussagekräftigere Namen für denselben Zweck. Für dich als Server-Admin ist multi-user.target der Normalzustand:
| systemd-Target | Beschreibung | Altes Runlevel |
|---|---|---|
poweroff.target | System herunterfahren | 0 |
rescue.target | Einzelbenutzer-Wartungsmodus | 1 |
multi-user.target | Mehrbenutzerbetrieb ohne GUI – Standard für Server | 3 |
graphical.target | Desktop mit grafischer Anmeldung | 5 |
reboot.target | System neu starten | 6 |
# Aktuelles Standard-Target anzeigen:
systemctl get-default
# Auf Servern typisch: multi-user.target
# Standard-Target setzen (nur wenn nötig):
sudo systemctl set-default multi-user.target Logs mit journalctl lesen
# Alle Logs (neueste zuerst, 'q' zum Beenden):
journalctl -e
# Nur Logs des aktuellen Boots:
journalctl -b
# Nur Fehlermeldungen:
journalctl -b -p err
# Logs eines bestimmten Dienstes:
journalctl -u ssh
journalctl -u nginx -f
# -f = live mitlesen (wie tail -f), beenden mit Strg+C
# Logs der letzten Stunde:
journalctl --since "1 hour ago" Wenn ein Dienst nicht startet, zeigt systemctl status dienstname oft kryptische Fehlermeldungen. Kopiere die vollständige Ausgabe und frag: „Mein nginx startet nicht. Hier ist die systemctl-status-Ausgabe. Was bedeutet der Fehler und wie behebe ich ihn auf Ubuntu 24.04?"
Umgebungsvariablen sind benannte Werte, die für alle Programme sichtbar sind, die in deiner Shell-Sitzung laufen. Die wichtigste ist PATH – sie sagt der Shell, in welchen Verzeichnissen sie nach Programmen suchen soll.
Stell dir das vor wie eine Liste von Schubladen, in denen die Shell nach Werkzeugen sucht. Gibst du ls ein, schaut die Shell die PATH-Verzeichnisse der Reihe nach durch, bis sie das Programm findet.
# Alle Umgebungsvariablen anzeigen:
env
printenv
# Eine bestimmte Variable lesen:
echo $PATH
echo $HOME
echo $USER
# Variable für die aktuelle Sitzung setzen:
export MEINE_VAR="hallo"
echo $MEINE_VAR
# Gilt nur in dieser Shell-Sitzung – nach logout weg
# Variable dauerhaft für alle Benutzer setzen:
sudo nano /etc/environment
# Dort eintragen: MEINE_VAR="hallo"
# Gilt nach dem nächsten Login systemweit | Variable | Bedeutung | Beispielwert |
|---|---|---|
PATH | Suchpfade für Programme | /usr/bin:/usr/sbin:/bin |
HOME | Home-Verzeichnis des Benutzers | /home/admin |
USER | Aktueller Benutzername | admin |
SHELL | Aktive Shell | /bin/bash |
LANG | Sprache und Zeichensatz | de_DE.UTF-8 |
EDITOR | Standard-Texteditor | vim oder nano |
Es gibt drei Stellen: /etc/environment (systemweit, für alle Benutzer), ~/.bashrc oder ~/.profile (nur für einen Benutzer, bei jedem Login) und export VAR=wert im Terminal (nur für die aktuelle Sitzung). Für Server-weite Einstellungen nimm /etc/environment.
Wenn dein Server langsam wird oder unerwartet viel CPU oder RAM verbraucht, führe ps aux --sort=-%cpu | head -15 aus, kopiere die Ausgabe und frag: „Welcher Prozess verursacht die hohe CPU-Auslastung auf meinem Ubuntu-Server? Ist das normal und was kann ich dagegen tun?"
Du verschaffst dir einen vollständigen Überblick über deinen Server – Distribution, Kernel, Partitionen und Ressourcen. Das ist die Basis für alle weiteren Module.
cat /etc/os-release und uname -r. Notiere die Ergebnisse.ls /sys/firmware/efi/ 2>/dev/null && echo "UEFI" || echo "BIOS"lsblk. Wie heißt deine Root-Partition (Mountpoint /)? Wie groß ist sie?df -h. Wie viel Prozent sind auf der Root-Partition belegt?free -h und lscpu | head -10. Wie viele CPU-Kerne hat dein Server?Lösungshinweise anzeigen
Bei lsblk erkennst du die Root-Partition am Mountpoint /. Auf VMs heißt sie oft /dev/vda1, auf physischen Servern /dev/sda2.
Bei df -h zeigt die Spalte „Use%" den Füllstand. Unter 80 % ist in der Regel unbedenklich. Über 90 % wird es eng – dann Logs prüfen und aufräumen.
free -h: Schau auf die Zeile „Mem:" – „total" ist dein RAM, „available" ist noch frei nutzbar.
Du lernst, welche Dienste auf deinem Server laufen, wie du ihren Status prüfst und wie du Prozesse beobachtest. Das brauchst du täglich in der Server-Administration.
systemctl list-units --type=service --state=running. Zähle, wie viele Dienste aktiv sind.systemctl status ssh. Ist er aktiv? Seit wann läuft er?ps aux --sort=-%cpu | head -10. Welcher Prozess verbraucht am meisten CPU?journalctl -u ssh -n 20. Siehst du deine eigenen Login-Versuche mit Zeitstempel und IP?systemctl get-default. Auf einem Server sollte multi-user.target stehen.Lösungshinweise anzeigen
Bei systemctl status ssh siehst du in der grünen Zeile „active (running)" wenn der Dienst läuft. Die Zeile „Active:" zeigt, seit wann er läuft.
In den SSH-Logs erscheinen alle Logins mit Benutzername, IP-Adresse und Zeitstempel. So erkennst du auch unerlaubte Login-Versuche fremder IPs.
Ein frischer Debian/Ubuntu-Server hat typischerweise 20–35 laufende Dienste.
Du erkundest die FHS-Verzeichnisstruktur und lernst, wie Umgebungsvariablen funktionieren. Beides ist Grundlage für Konfigurationsarbeit in allen späteren Modulen.
ls /. Erkennst du die Verzeichnisse aus der Tabelle oben?sudo du -sh /* 2>/dev/null | sort -hr | head -10. Was steht an erster Stelle?echo $PATH. Wie viele Verzeichnisse sind durch Doppelpunkt getrennt aufgelistet?export TESTVAR="Linux-Kurs" und zeige sie an: echo $TESTVAR. Logge dich aus und wieder ein – ist die Variable noch da?journalctl -n 20 --no-pager. Kannst du erkennen, was in den letzten Minuten auf dem Server passiert ist?Lösungshinweise anzeigen
Die größten Verzeichnisse sind typischerweise /usr (2–5 GB – alle Programme) und /var (wächst mit der Zeit durch Logs und Datenbanken).
Ein typischer PATH enthält 5–8 Einträge: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin. Wenn du ein eigenes Programm systemweit verfügbar machen willst, legst du es in /usr/local/bin/ ab.
Nach dem Ausloggen ist $TESTVAR weg – export gilt nur für die aktuelle Shell-Sitzung. Für dauerhafte Variablen trägst du sie in ~/.bashrc oder /etc/environment ein.
mount & /etc/fstab
Eine Festplatte anschließen reicht unter Linux nicht -- du musst sie erst einhängen (mounten), bevor du darauf zugreifen kannst. In diesem Modul lernst du, wie du Partitionen und USB-Sticks einbindest, dauerhaft in der fstab einträgst und typische Fehler vermeidest.
In diesem Modul arbeitest du ausschließlich auf dem Server. Alle Befehle werden dort ausgeführt -- du bist per SSH eingeloggt (aus Modul 02). Es gibt keinen Wechsel zum lokalen Rechner.
Unter Windows steckst du einen USB-Stick ein und er taucht automatisch als Laufwerk D: auf. Unter Linux passiert das nicht von allein. Du musst das Gerät erst an einen Ordner im Verzeichnisbaum einhängen -- das nennt sich mounten.
Stell dir den Verzeichnisbaum wie einen Kleiderschrank vor. Der Schrank hat Fächer (/home, /var, /mnt). Wenn du eine neue Festplatte einhängst, schiebst du ein zusätzliches Fach in den Schrank -- zum Beispiel bei /mnt/daten. Ab diesem Moment sind alle Dateien dieser Festplatte über diesen Ordner erreichbar.
Der Einhängepunkt (Mountpoint) ist einfach ein leerer Ordner, den du vorher mit mkdir erstellst. Nach dem Mounten zeigt dieser Ordner auf die Festplatte -- nicht auf den internen Speicher des Servers.
Bevor du etwas mountest, musst du wissen, wie das Gerät heißt und welche Partitionen es hat. Dafür gibt es zwei Werkzeuge:
lsblk zeigt dir alle Blockgeräte (Festplatten, Partitionen, USB-Sticks) in einer Baumstruktur:
lsblk
# Ausgabe-Beispiel:
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
# sda 8:0 0 20G 0 disk
# ├─sda1 8:1 0 19G 0 part /
# └─sda2 8:2 0 1G 0 part [SWAP]
# sdb 8:16 0 500G 0 disk
# └─sdb1 8:17 0 500G 0 part
# Mit Dateisystem und UUID anzeigen:
lsblk -f blkid zeigt dir die UUID und den Dateisystemtyp jeder Partition -- das brauchst du für die fstab:
sudo blkid
# Ausgabe-Beispiel:
# /dev/sda1: UUID="550e8400-e29b-41d4-a716-446655440000" TYPE="ext4"
# /dev/sdb1: UUID="a1b2c3d4-5678-90ab-cdef-1234567890ab" TYPE="ext4"
# Nur eine bestimmte Partition abfragen:
sudo blkid /dev/sdb1 | Befehl | Zeigt | Wann verwenden |
|---|---|---|
lsblk | Geräte und Partitionen als Baum | Überblick verschaffen, Gerätename finden |
lsblk -f | Wie oben + Dateisystem + UUID | Alle Infos auf einen Blick |
sudo blkid | UUID und Dateisystemtyp aller Partitionen | UUID für fstab-Eintrag kopieren |
df -hT | Belegung aller gemounteten Partitionen | Füllstand und freien Platz prüfen |
findmnt | Alle Mounts als Baumstruktur | Was ist gerade wo eingehängt? |
Ein manueller Mount gilt nur bis zum nächsten Neustart. Er ist ideal zum Testen oder für USB-Sticks, die du nur gelegentlich anschließt.
Schritt 1: Erstelle einen Ordner als Einhängepunkt. Schritt 2: Hänge die Partition ein. Schritt 3: Prüfe das Ergebnis.
# Einhängepunkt erstellen (falls noch nicht vorhanden):
sudo mkdir -p /mnt/daten
# Partition einhängen:
sudo mount /dev/sdb1 /mnt/daten
# Prüfen ob es geklappt hat:
df -h /mnt/daten
ls /mnt/daten/ Zum Aushängen nimmst du umount. Das ist kein Tippfehler -- der Befehl heißt wirklich umount, nicht unmount:
# Partition aushängen:
sudo umount /mnt/daten
# Falls "target is busy" kommt -- wer nutzt die Partition noch?
sudo lsof /mnt/daten
# Zeigt alle Prozesse, die Dateien auf der Partition offen haben Ein Speichergerät ohne umount zu entfernen kann zu Datenverlust führen. Das Dateisystem puffert Schreibvorgänge -- beim Aushängen werden diese Puffer erst auf die Platte geschrieben (Flush). Immer erst sudo umount, dann abstecken.
Beim Mounten kannst du mit -o Optionen angeben, die das Verhalten steuern. Mehrere Optionen werden durch Komma getrennt -- ohne Leerzeichen:
# Nur-Lesen mounten (Partition kann nicht verändert werden):
sudo mount -o ro /dev/sdb1 /mnt/backup
# Mehrere Optionen kombinieren:
sudo mount -o rw,noexec,noatime /dev/sdb1 /mnt/daten
# ISO-Datei mounten (als Loop-Device):
sudo mount -o loop /tmp/debian.iso /mnt/iso | Option | Bedeutung | Wann sinnvoll |
|---|---|---|
defaults | Standardpaket: rw, suid, dev, exec, auto | Normaler Datenträger |
ro | Nur-Lesen (read-only) | Backup-Medien, ISO-Dateien |
rw | Lesen und Schreiben | Standard für Datenpartitionen |
noexec | Keine Programme ausführen | Sicherheit: /tmp, Upload-Ordner |
noatime | Keine Zugriffszeiten schreiben | Performance bei SSDs |
nofail | Boot nicht abbrechen wenn Mount fehlschlägt | Externe Platten, Netzlaufwerke |
_netdev | Warten bis Netzwerk bereit ist | NFS, CIFS, SSHFS |
Ein manueller mount-Befehl ist nach dem Neustart vergessen. Wenn eine Partition automatisch beim Booten eingehängt werden soll, trägst du sie in /etc/fstab ein. Diese Datei liest Linux beim Start und hängt alle eingetragenen Partitionen automatisch ein.
Die sechs Felder der fstab
So sieht eine typische fstab aus:
# Feld 1: Gerät Feld 2: Mountpoint Feld 3: FS Feld 4: Optionen Feld 5 Feld 6
UUID=550e8400-e29b-41d4-a716-446655440000 / ext4 errors=remount-ro 0 1
UUID=a1b2c3d4-5678-90ab-cdef-1234567890ab /home ext4 defaults 0 2
/dev/sdb1 /mnt/daten ext4 defaults,nofail 0 2
# Swap-Partition:
UUID=dead1234-beef-5678-abcd-ef0123456789 none swap sw 0 0 | Feld | Bedeutung | Beispiel |
|---|---|---|
| 1 -- Gerät | Welche Partition (UUID empfohlen!) | UUID=550e8400-... |
| 2 -- Mountpoint | Wo im Verzeichnisbaum einhängen | /mnt/daten |
| 3 -- Dateisystem | Typ des Dateisystems | ext4, vfat, swap |
| 4 -- Optionen | Mount-Optionen, Komma-getrennt | defaults,nofail |
| 5 -- Dump | Backup-Flag -- fast immer 0 | 0 |
| 6 -- Pass | fsck-Reihenfolge beim Booten: 1=Root, 2=andere, 0=überspringen | 2 |
Eine einzige falsche Zeile in /etc/fstab kann dazu führen, dass dein Server beim nächsten Neustart hängen bleibt und nicht mehr startet. Deshalb gilt: Immer zuerst mit sudo mount -a testen, bevor du neustartest. Nutze außerdem nofail bei externen Platten -- dann bootet das System auch, wenn das Gerät mal nicht angeschlossen ist.
UUID vs. Gerätename -- warum UUID sicherer ist
Gerätenamen wie /dev/sdb1 sind nicht fix. Wenn du eine zweite Festplatte einbaust oder den Server umbaust, kann aus sdb plötzlich sdc werden -- und dein fstab-Eintrag zeigt auf die falsche Partition.
Die UUID (Universally Unique Identifier) hingegen ist eine einmalige Kennung, die beim Formatieren vergeben wird und sich nie ändert -- egal in welchem SATA-Port die Platte steckt.
Der Gerätename /dev/sdb1 ist wie der Parkplatz in einer Tiefgarage -- morgen steht da vielleicht ein anderes Auto. Die UUID ist wie die FIN (Fahrzeugidentifikationsnummer) -- die bleibt immer gleich, egal wo das Fahrzeug steht.
Neue Partition in die fstab eintragen
Das Vorgehen Schritt für Schritt:
# 1. UUID der Partition herausfinden:
sudo blkid /dev/sdb1
# Ausgabe: /dev/sdb1: UUID="a1b2c3d4-..." TYPE="ext4"
# 2. Einhängepunkt erstellen:
sudo mkdir -p /mnt/daten
# 3. fstab mit einem Editor öffnen und Zeile hinzufügen:
sudo nano /etc/fstab
# Neue Zeile einfügen (UUID durch echten Wert aus blkid ersetzen):
# UUID=a1b2c3d4-5678-90ab-cdef-1234567890ab /mnt/daten ext4 defaults,nofail 0 2
# 4. Testen ohne Neustart -- mountet alle fstab-Einträge:
sudo mount -a
# Kein Fehler = alles korrekt eingetragen
# 5. Ergebnis prüfen:
df -h /mnt/daten Die fstab-Syntax ist fehleranfällig -- ein falscher Eintrag und der Server startet nicht mehr. Gib einer KI die Ausgabe von sudo blkid und beschreibe dein Ziel: „Erstelle mir einen fstab-Eintrag für die Partition mit UUID a1b2c3d4-5678-90ab-cdef-1234567890ab, die ich als ext4 unter /mnt/daten dauerhaft einbinden will. Das System soll trotzdem booten, wenn die Platte mal nicht angeschlossen ist."
Wenn der Arbeitsspeicher (RAM) voll ist, lagert Linux selten genutzte Daten auf die Swap-Partition aus. Das ist langsamer als echter RAM, verhindert aber, dass Programme abstürzen oder der Server nicht mehr reagiert.
Statt einer eigenen Swap-Partition kannst du auch eine Swap-Datei anlegen -- das ist flexibler und auf modernen Systemen genauso schnell:
# Aktuellen Swap-Status anzeigen:
swapon --show
free -h
# Swap-Datei erstellen (2 GB):
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Dauerhaft in fstab eintragen (nach der letzten Zeile einfügen):
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Prüfen ob Swap aktiv ist:
swapon --show Faustregel: So viel Swap wie RAM, maximal 4--8 GB. Bei Servern mit 32 GB+ RAM reichen oft 2--4 GB Swap. Zu viel Swap kann schaden -- das System wird dann extrem langsam, statt den Speicherengpass klar anzuzeigen.
Du kannst in der fstab auch Netzlaufwerke dauerhaft einbinden. Das Prinzip ist dasselbe: Mountpoint erstellen, Eintrag in fstab, mount -a testen. Wichtig: Immer _netdev als Option angeben, damit Linux wartet bis das Netzwerk bereit ist.
# NFS-Share einbinden (Modul 12 behandelt das ausführlich):
# 192.168.1.10:/export/daten /mnt/nfs nfs defaults,_netdev,nofail 0 0
# Aktuell gemountete Netzlaufwerke anzeigen:
findmnt -t nfs,cifs SFTP, SSHFS und SAMBA-Freigaben werden in Modul 12 ausführlich behandelt. Dort lernst du auch, wie du Windows-Netzlaufwerke (CIFS/SAMBA) unter Linux einbindest.
Diese Befehle helfen dir, Mount-Probleme zu diagnostizieren:
# Alle aktuell gemounteten Dateisysteme als Baum:
findmnt
# fstab auf Syntaxfehler prüfen (vor dem Neustart!):
findmnt --verify
# Alle fstab-Einträge neu mounten (Test ohne Neustart):
sudo mount -a
# Kernel-Meldungen zu Festplatten anzeigen:
dmesg | grep -i 'sd\|error\|fail' | tail -20 | Fehlermeldung | Mögliche Ursache | Lösung |
|---|---|---|
wrong fs type | Falsches Dateisystem angegeben | sudo blkid prüfen, richtigen Typ eintragen |
UUID not found | UUID in fstab stimmt nicht | UUID mit sudo blkid neu kopieren |
target is busy | Prozess nutzt die Partition noch | sudo lsof /mnt/pfad und Prozess beenden |
permission denied | Fehlende sudo-Rechte | Befehl mit sudo wiederholen |
Wenn eine Partition sich nicht mounten lässt oder die Fehlermeldung kryptisch ist, kopiere die Ausgabe von sudo mount /dev/sdb1 /mnt/daten und dmesg | tail -20 und frag: „Ich bekomme beim Mounten von /dev/sdb1 die Fehlermeldung 'wrong fs type, bad option, bad superblock'. Hier ist die Ausgabe von sudo blkid /dev/sdb1 und dmesg tail. Was muss ich tun?"
Verschaffe dir einen vollständigen Überblick über alle Partitionen und eingehängten Dateisysteme deines Servers.
lsblk. Wie viele Festplatten hat dein Server? Welche Partitionen gibt es?lsblk -f. Notiere den Dateisystemtyp deiner Root-Partition (/).sudo blkid. Notiere die UUID deiner Root-Partition.cat /etc/fstab. Findest du die UUID aus Schritt 3 in der fstab wieder?findmnt --verify. Wenn keine Ausgabe kommt, ist alles in Ordnung.Lösungshinweise anzeigen
Bei lsblk siehst du die Geräte (sda, sdb...) und ihre Partitionen (sda1, sda2...). Die Root-Partition hat in der Spalte MOUNTPOINT den Wert /.
In /etc/fstab steht die Root-Partition mit pass=1 (letzte Spalte) -- das bedeutet, sie wird beim Booten als erste auf Fehler geprüft. Alle anderen Partitionen haben pass=2 oder pass=0.
Du übst das manuelle Einhängen und Aushängen mit einem sicheren Test-Dateisystem. Kein Risiko für deine echten Daten.
dd if=/dev/zero of=/tmp/testdisk bs=1M count=100. Das dauert einige Sekunden.sudo mkfs.ext4 /tmp/testdisk. Bestätige mit y wenn gefragt.sudo mkdir -p /mnt/test && sudo mount -o loop /tmp/testdisk /mnt/test.df -h /mnt/test. Schreibe eine Testdatei: echo "Hallo Welt" | sudo tee /mnt/test/test.txt.sudo umount /mnt/test && sudo rmdir /mnt/test && rm /tmp/testdisk.Lösungshinweise anzeigen
dd erstellt eine leere Datei fixer Größe. mkfs.ext4 schreibt ein Dateisystem hinein -- danach verhält sie sich genau wie eine echte Partition. mount -o loop hängt sie als Loop-Device ein.
Nach dem umount ist die Testdatei nicht mehr eingehängt -- die Datei selbst existiert noch unter /tmp/testdisk, bis du sie mit rm löschst.
Du trägst eine Partition dauerhaft in die fstab ein und testest den Eintrag sicher -- ohne den Server neustarten zu müssen.
sudo cp /etc/fstab /etc/fstab.bak. Diese Kopie rettet dich falls etwas schiefgeht.sudo blkid. Wähle eine Partition, die noch nicht in der fstab steht. Falls keine vorhanden: Erstelle eine Testdatei mit dd if=/dev/zero of=/tmp/testdisk bs=1M count=100 && sudo mkfs.ext4 /tmp/testdisk.sudo mkdir -p /mnt/testdaten. Öffne die fstab im Editor: sudo nano /etc/fstab. Füge am Ende eine neue Zeile hinzu mit UUID, Mountpoint, Dateisystemtyp und den Optionen defaults,nofail.sudo mount -a. Kein Fehler bedeutet: Eintrag ist korrekt. Prüfe mit df -h /mnt/testdaten.findmnt --verify. Bei Problemen kannst du die Sicherheitskopie wiederherstellen: sudo cp /etc/fstab.bak /etc/fstab.Lösungshinweise anzeigen
Eine korrekte fstab-Zeile sieht so aus (sechs Felder, durch Leerzeichen oder Tabs getrennt):UUID=a1b2c3d4-... /mnt/testdaten ext4 defaults,nofail 0 2
Wenn mount -a eine Fehlermeldung ausgibt: Prüfe ob die UUID korrekt kopiert wurde und ob der Mountpoint-Ordner existiert. Die Sicherheitskopie unter /etc/fstab.bak kannst du jederzeit wiederherstellen.
Datei-Berechtigungen
Wer darf lesen, wer darf schreiben, wer darf ausführen? Datei-Berechtigungen sind das Fundament der Linux-Sicherheit. Du lernst, was drwxr-xr-x bedeutet, wie du Berechtigungen mit chmod setzt und warum chmod 777 fast immer ein Fehler ist.
Berechtigungen verwaltest du direkt auf dem Server – nach dem SSH-Login aus Modul 02. Alle Befehle in diesem Modul gibst du im Server-Terminal ein.
Alle Befehle in diesem Modul laufen auf dem Server – du bist bereits per SSH eingeloggt.
Stell dir jede Datei wie einen Werkzeugschrank vor: Es gibt einen Besitzer (der Schlosser, dem er gehört), eine Gruppe (das Team mit einem Schlüssel) und alle anderen (die ohne Schlüssel). Für jede dieser drei Gruppen legst du fest, was erlaubt ist.
Linux kennt drei Grundrechte:
| Recht | Buchstabe | Oktalwert | Bedeutung für Dateien | Bedeutung für Verzeichnisse |
|---|---|---|---|---|
| Lesen | r | 4 | Inhalt lesen | Inhalt auflisten (ls) |
| Schreiben | w | 2 | Inhalt ändern | Dateien erstellen / löschen |
| Ausführen | x | 1 | Als Programm starten | In Verzeichnis wechseln (cd) |
ls -la lesen – was bedeuten die Zeichen?
Mit ls -la siehst du alle Dateien inklusive ihrer Berechtigungen:
ls -la
# Beispiel-Ausgabe:
drwxr-xr-x 2 max entwickler 4096 Jan 15 10:30 projekt/
-rw-r--r-- 1 max entwickler 2048 Jan 15 10:28 readme.txt
-rwxr-x--- 1 root root 8192 Jan 14 09:00 backup.sh So liest du die erste Zeile drwxr-xr-x:
| Position | Zeichen | Bedeutung |
|---|---|---|
| 1 | d | Typ: d = Verzeichnis, - = Datei, l = Symlink |
| 2–4 | rwx | Besitzer darf: lesen + schreiben + ausführen |
| 5–7 | r-x | Gruppe darf: lesen + ausführen (kein Schreiben) |
| 8–10 | r-x | Andere dürfen: lesen + ausführen (kein Schreiben) |
Teile die 9 Buchstaben nach dem Typ-Zeichen immer in Dreiergruppen: rwx | r-x | r-x = Besitzer | Gruppe | Andere. Ein - an einer Stelle bedeutet: dieses Recht fehlt.
chmod (change mode) ändert die Berechtigungen einer Datei oder eines Verzeichnisses. Es gibt zwei Schreibweisen: symbolisch (mit Buchstaben) und oktal (mit Zahlen).
Symbolische Schreibweise
Buchstaben-Syntax: u = Besitzer (user), g = Gruppe, o = Andere (others), a = Alle. Operator: + hinzufügen, - entfernen, = genau setzen.
# Besitzer darf ausführen:
chmod u+x script.sh
# Gruppe darf schreiben:
chmod g+w datei.txt
# Anderen alle Rechte entziehen:
chmod o-rwx geheim.txt
# Allen Lese-Recht geben:
chmod a+r public.html
# Besitzer: rwx, Gruppe: rx, Andere: nichts:
chmod u=rwx,g=rx,o= backup.sh Oktale Schreibweise – die Zahlen-Methode
Die Oktalschreibweise fasst die Rechte jeder Gruppe in eine Zahl zusammen. Das Prinzip ist wie beim Schlosserzählwerk: Jedes Recht hat einen festen Wert – du addierst, was du brauchst.
| Recht | Wert | Merkhilfe |
|---|---|---|
r – Lesen | 4 | 4 = der größte Einzelwert |
w – Schreiben | 2 | 2 = halb so viel |
x – Ausführen | 1 | 1 = der kleinste |
Drei Stellen, drei Gruppen: 755 = Besitzer | Gruppe | Andere. Jede Stelle ist die Summe der erlaubten Rechte:
| Oktalzahl | Rechte (rwx) | Formel |
|---|---|---|
7 | rwx | 4+2+1 = lesen + schreiben + ausführen |
6 | rw- | 4+2 = lesen + schreiben |
5 | r-x | 4+1 = lesen + ausführen |
4 | r-- | 4 = nur lesen |
0 | --- | 0 = kein Zugriff |
Die häufigsten Oktalwerte in der Praxis:
# 755 = rwxr-xr-x (Standard für Verzeichnisse und Skripte)
chmod 755 script.sh
# 644 = rw-r--r-- (Standard für normale Dateien)
chmod 644 config.txt
# 600 = rw------- (nur Besitzer darf lesen/schreiben – z.B. SSH-Schlüssel)
chmod 600 ~/.ssh/id_ed25519
# 700 = rwx------ (nur Besitzer, voller Zugriff)
chmod 700 ~/privat/
# Rekursiv für ein ganzes Verzeichnis und alle Inhalte:
chmod -R 755 /var/www/html/ 755 – Standard für Verzeichnisse und Programme: jeder kann lesen und ausführen, nur Besitzer schreibt.
644 – Standard für normale Dateien: jeder kann lesen, nur Besitzer schreibt.
600 – Geheime Dateien: nur Besitzer liest und schreibt (SSH-Schlüssel, Passwort-Dateien).
640 – Konfigurationsdateien: Besitzer liest/schreibt, Gruppe liest, Andere: nichts.
chmod 777 gibt jedem Benutzer auf dem System volle Lese-, Schreib- und Ausführungsrechte – auch fremden Prozessen und Angreifern. Das ist ein häufiger Anfängerfehler. Wenn du „Permission denied" bekommst, ist chmod 777 keine Lösung. Finde stattdessen heraus, welcher Benutzer und welche Gruppe die richtige Berechtigung braucht – und setze nur die.
chown (change owner) ändert, wem eine Datei gehört. Das brauchst du z.B. wenn du Webserver-Dateien für den Benutzer www-data freigeben willst.
# Besitzer ändern (braucht sudo):
sudo chown max datei.txt
# Besitzer und Gruppe in einem Schritt ändern:
sudo chown max:entwickler datei.txt
# Nur die Gruppe ändern:
sudo chgrp entwickler datei.txt
# Rekursiv für ein ganzes Verzeichnis:
sudo chown -R www-data:www-data /var/www/html/
# Aktuellen Besitzer prüfen:
ls -la datei.txt
# -rw-r--r-- 1 max entwickler 2048 Jan 15 datei.txt
# ^ Besitzer ^ Gruppe Die umask ist wie ein Filter: Sie bestimmt, welche Rechte bei neu erstellten Dateien und Verzeichnissen automatisch weggelassen werden. Der Standard-Wert ist 022.
| umask | Neue Dateien | Neue Verzeichnisse | Wirkung |
|---|---|---|---|
022 | 644 (rw-r--r--) | 755 (rwxr-xr-x) | Standard – Gruppe und Andere dürfen lesen |
027 | 640 (rw-r-----) | 750 (rwxr-x---) | Restriktiver – Andere haben keinen Zugriff |
077 | 600 (rw-------) | 700 (rwx------) | Streng – nur Besitzer hat Zugriff |
# Aktuelle umask anzeigen:
umask
# Ausgabe: 0022
# umask für die aktuelle Sitzung ändern:
umask 027
# Dauerhaft ändern – in ~/.bashrc eintragen:
echo "umask 027" >> ~/.bashrc Neben den normalen rwx-Rechten gibt es drei Spezialrechte. Du wirst ihnen im Alltag begegnen – hier ist, was sie bedeuten:
| Bit | Oktal | Wirkung auf Dateien | Wirkung auf Verzeichnisse |
|---|---|---|---|
| SUID | 4000 | Programm läuft mit Rechten des Besitzers (z.B. als root) | – |
| SGID | 2000 | Programm läuft mit Rechten der Gruppe | Neue Dateien erben die Gruppe des Verzeichnisses |
| Sticky Bit | 1000 | – | Nur der Besitzer darf seine eigenen Dateien löschen |
Beispiele: SUID beim Programm passwd, SGID für Teamverzeichnisse, Sticky Bit beim Ordner /tmp:
# SUID-Beispiel: passwd läuft als root, auch wenn ein normaler User es startet:
ls -la /usr/bin/passwd
# -rwsr-xr-x 1 root root ... /usr/bin/passwd
# ^ das 's' statt 'x' zeigt SUID an
# SGID auf Verzeichnis setzen (neue Dateien erben die Gruppe):
sudo chmod g+s /shared/projekt/
# oder mit Oktal: sudo chmod 2775 /shared/projekt/
# Sticky Bit anschauen – /tmp hat es standardmäßig:
ls -ld /tmp
# drwxrwxrwt ← das 't' am Ende zeigt das Sticky Bit
# Sticky Bit manuell setzen:
sudo chmod +t /shared/uploads/ SGID auf einem Teamverzeichnis ist sehr praktisch: Wenn das Verzeichnis der Gruppe entwickler gehört und SGID gesetzt ist, erben alle neuen Dateien automatisch diese Gruppe – egal wer sie erstellt. So können alle Teammitglieder zusammenarbeiten, ohne ständig chgrp aufzurufen.
Hier sind die richtigen Berechtigungen für die häufigsten Szenarien – als schnelle Referenz:
| Szenario | Empfohlene Berechtigungen | Warum |
|---|---|---|
Webserver-Dateien (/var/www/html/) | sudo chown -R www-data:www-data + 755 / 644 | Webserver-Prozess braucht Lesezugriff |
SSH-Schlüssel (~/.ssh/id_ed25519) | chmod 600 | SSH verweigert Verbindung, wenn der Schlüssel zu offen ist |
| Gemeinsamer Projektordner | chmod 2775 (SGID) | Neue Dateien erben automatisch die Gruppe |
| Bash-Skript | chmod 755 oder chmod u+x | Ausführbar für Besitzer, lesbar für alle |
| Konfigurationsdatei mit Passwort | chmod 600 oder 640 | Passwörter gehören nicht in die Hände Anderer |
| Upload-Verzeichnis | chmod 1777 (Sticky Bit) | Jeder darf schreiben, aber nur eigene Dateien löschen |
„Permission denied" ist eine der häufigsten Fehlermeldungen unter Linux. Wenn du nicht weiterkommst, zeige der KI die Ausgabe von ls -la und id und frag: „Ich bekomme 'Permission denied' wenn ich /var/www/html/index.html bearbeiten will. Hier sind die Berechtigungen und mein aktueller Benutzer – was muss ich ändern und warum?"
Du übst das Lesen und Ändern von Dateiberechtigungen – das Handwerkszeug für die sichere Serververwaltung.
touch ~/testdatei.txt && ls -la ~/testdatei.txt. Welche Berechtigungen hat die Datei direkt nach dem Anlegen?640: chmod 640 ~/testdatei.txt. Zeige sie erneut mit ls -la ~/testdatei.txt. Was hat sich geändert?chmod u+x ~/testdatei.txt. Wie lautet die Berechtigungskette jetzt?chmod o= ~/testdatei.txt. Zeige die Berechtigungen erneut.644 und räume auf: chmod 644 ~/testdatei.txt && rm ~/testdatei.txtLösungshinweise anzeigen
Nach touch hat die Datei typischerweise -rw-r--r-- (644) – der Standard, den die umask 022 ergibt. Nach chmod 640 wird sie zu -rw-r-----: Andere haben jetzt keinen Zugriff mehr.
Nach chmod u+x steht -rwxr----- (740). Das x beim Besitzer ist hinzugekommen. Beim Entziehen mit o= bleibt das Ergebnis gleich, weil Andere ohnehin schon nichts durften.
Du richtest ein Verzeichnis für Teamarbeit ein – mit korrekten Gruppen-Berechtigungen und SGID, damit alle Teammitglieder reibungslos zusammenarbeiten können.
sudo mkdir -p /shared/projektprojekt (aus Modul 08): sudo chgrp projekt /shared/projektsudo chmod 2775 /shared/projekttouch /shared/projekt/test.txt. Prüfe mit ls -la /shared/projekt/ – welche Gruppe hat die neue Datei?ls -ld /shared/projekt. Siehst du das s in den Gruppen-Rechten (drwxrwsr-x)?Lösungshinweise anzeigen
Dank SGID (das s bei den Gruppenrechten) erbt die neue Datei automatisch die Gruppe projekt – egal welcher Benutzer sie erstellt hat. Ohne SGID würde die primäre Gruppe des Erstellers gesetzt, was zu Problemen im Team führt.
2775 setzt sich zusammen aus: SGID (2) + Besitzer rwx (7) + Gruppe rwx (7) + Andere rx (5).
SSH verweigert die Verbindung, wenn der private Schlüssel falsche Berechtigungen hat. Du übst, die richtigen Berechtigungen für sicherheitskritische Dateien zu prüfen und zu setzen.
ls -la ~/.ssh/. Was siehst du?~/.ssh/ sollte 700 haben: chmod 700 ~/.ssh. Prüfe das Ergebnis.authorized_keys sollte 600 haben: chmod 600 ~/.ssh/authorized_keys. Prüfe das Ergebnis.ls -la ~/.ssh/ nochmals und erkläre in eigenen Worten, was 700 und 600 hier konkret bedeuten.Lösungshinweise anzeigen
chmod 700 ~/.ssh bedeutet: Nur du (der Besitzer) darfst das Verzeichnis lesen, beschreiben und betreten. Gruppe und Andere sehen gar nichts.
chmod 600 ~/.ssh/authorized_keys bedeutet: Nur du darfst die Datei lesen und schreiben. Wenn diese Datei für andere lesbar wäre, könnte SSH theoretisch fremde Schlüssel enthalten, ohne dass es auffällt – deshalb erzwingt SSH die strengen Berechtigungen.
Wenn du unsicher bist, was ein bestimmter Oktalwert bedeutet oder welchen du für einen Anwendungsfall brauchst, frag direkt: „Was bedeutet chmod 2750 genau? Wer darf was? Und welchen chmod-Wert brauche ich, wenn der Besitzer alles darf, die Gruppe nur lesen und ausführen, und alle anderen gar nichts?"
SFTP, SSHFS & SAMBA
Netzlaufwerke verbinden Server und Clients -- ob Linux, Windows oder Mac. Du lernst drei Wege, Dateien übers Netz zu teilen: SFTP für sichere Übertragungen, SSHFS zum Einbinden als lokales Laufwerk und SAMBA für die Windows-Welt.
In diesem Modul arbeitest du gleichzeitig auf zwei Rechnern. SFTP und SSHFS bedienst du von deinem Rechner aus. SAMBA installierst und konfigurierst du auf dem Server. Ab jetzt markieren wir immer klar, wo du gerade bist:
Befehle, die du in deinem eigenen Terminal eingibst -- SFTP-Client, SSHFS-Mount, Windows-Zugriff.
Befehle, die auf dem entfernten Server laufen -- SAMBA installieren, smb.conf konfigurieren, Nutzer anlegen.
Es gibt nicht den einen Weg, Dateien über das Netzwerk zu teilen. Welcher am besten passt, hängt davon ab, wer auf die Daten zugreifen soll und wie:
| Protokoll | Beste für | Sicherheit | Aufwand |
|---|---|---|---|
| SFTP | Dateiübertragung, Skripte, einzelne Dateien | Sehr hoch (SSH) | Sofort -- kein extra Setup |
| SSHFS | Linux-Clients, dauerhaftes Einbinden | Sehr hoch (SSH) | Gering -- nur Client-Paket |
| SAMBA | Windows-Clients, Heimnetz, Mixed-OS | Mittel (SMB) | Mittel -- Server konfigurieren |
SFTP ist wie ein sicherer Kurier -- du schickst Pakete einzeln, gezielt, direkt. SSHFS ist wie ein Briefkasten direkt in deiner Wohnung -- der Server-Ordner sieht aus wie ein lokaler Ordner auf deinem Rechner. SAMBA ist wie die Post im Büronetz -- alle im Netz greifen auf den gemeinsamen Ordner zu, sogar Windows-Nutzer ohne Extra-Software.
SFTP (SSH File Transfer Protocol) ist kein eigener Dienst, sondern ein Subsystem von SSH. Auf jedem Server, auf dem SSH läuft, ist SFTP bereits aktiv -- du brauchst nichts extra zu installieren.
Eine SFTP-Sitzung öffnest du wie eine SSH-Verbindung -- nur mit sftp statt ssh. Danach landest du in einem interaktiven Prompt, wo du Dateien übertragen kannst.
sftp benutzer@192.168.1.10
# Authentifizierung wie bei SSH (Schlüssel oder Passwort)
# Du landest im SFTP-Prompt: sftp> SFTP-Befehle im Überblick
Im SFTP-Prompt stehen dir diese Befehle zur Verfügung:
| Befehl | Bedeutung | Beispiel |
|---|---|---|
ls | Dateien auf dem Server auflisten | ls /home/admin |
lls | Dateien auf deinem Rechner auflisten | lls ~/Downloads |
cd ordner | Verzeichnis auf dem Server wechseln | cd /var/www |
lcd ordner | Verzeichnis auf deinem Rechner wechseln | lcd ~/Downloads |
get datei | Datei vom Server herunterladen | get nginx.conf |
put datei | Datei auf den Server hochladen | put config.txt /tmp/ |
mkdir name | Verzeichnis auf dem Server erstellen | mkdir backup |
exit | SFTP-Sitzung beenden | exit |
Praktisches Beispiel: Du lädst eine Konfigurationsdatei auf den Server und holst anschließend ein Log zurück:
# Aktuellen Ordner auf dem Server anzeigen:
sftp> pwd
# Datei hochladen:
sftp> put meine-config.conf /etc/nginx/conf.d/
# Ganzen Ordner herunterladen (-r = rekursiv):
sftp> get -r /var/log/nginx ./logs/
# Fertig -- Verbindung schliessen:
sftp> exit Nutze sftp, wenn du interaktiv mehrere Dateien verwalten oder den Server-Ordner erst erkunden willst. Nutze scp (aus Modul 02) für schnelle Einzelübertragungen in Skripten. Für große Synchronisierungen zwischen Verzeichnissen ist rsync (Modul 23) die bessere Wahl.
SSHFS (SSH Filesystem) lässt dich einen Ordner vom Server so einbinden, als wäre er ein lokales Verzeichnis auf deinem Rechner. Du siehst die Dateien im Dateimanager, bearbeitest sie wie gewohnt -- alle Änderungen landen über SSH auf dem Server. Der Server braucht kein extra Paket -- nur SSH muss laufen.
SSHFS wird auf dem Client (deinem Rechner) installiert, nicht auf dem Server.
sudo apt install sshfs Dann einen Einhängepunkt erstellen und den Server-Ordner einbinden:
# Einhängepunkt erstellen:
mkdir -p ~/mnt/server
# Server-Verzeichnis einbinden:
sshfs benutzer@192.168.1.10:/home/benutzer ~/mnt/server
# Mit Optionen (empfohlen -- stabilere Verbindung):
sshfs -o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 benutzer@192.168.1.10:/home/benutzer ~/mnt/server
# Prüfen ob eingebunden:
df -h
# Laufwerk wieder aushängen:
fusermount -u ~/mnt/server SSHFS dauerhaft via /etc/fstab
Damit der Server-Ordner nach jedem Neustart automatisch eingehängt wird, trägst du ihn in /etc/fstab ein -- genauso wie in Modul 10 für lokale Laufwerke.
# Format: user@server:/pfad /lokaler/mountpunkt fuse.sshfs optionen 0 0
benutzer@192.168.1.10:/home/benutzer /home/lokal/mnt/server fuse.sshfs defaults,_netdev,reconnect,uid=1000,gid=1000,IdentityFile=/home/lokal/.ssh/id_ed25519 0 0 Die Option _netdev sagt dem System: "Dieses Laufwerk braucht eine Netzwerkverbindung." Ohne sie kann der Boot-Vorgang hängen bleiben, wenn das Netzwerk noch nicht bereit ist -- der Rechner wartet dann ewig auf ein Laufwerk, das noch nicht erreichbar ist.
SAMBA ist wie ein Netzlaufwerk aus Windows -- du siehst den Server-Ordner direkt im Explorer, kannst Dateien rein- und rausschieben, als wäre es ein lokales Laufwerk. SAMBA implementiert das SMB-Protokoll, das Windows-Rechner von Haus aus verstehen. Installiert und konfiguriert wird es auf dem Server.
Zuerst SAMBA installieren und die Dienste starten:
sudo apt update
sudo apt install samba
# Status prüfen (smbd = Dateidienst, nmbd = Namensauflösung):
sudo systemctl status smbd nmbd
# Dienste beim Start automatisch aktivieren:
sudo systemctl enable smbd nmbd
# Firewall-Freigabe, falls ufw aktiv (aus Modul 14):
sudo ufw allow Samba smb.conf -- die Konfigurationsdatei
Die zentrale Konfiguration liegt in /etc/samba/smb.conf. Die Datei ist in Sektionen aufgeteilt: [global] für allgemeine Einstellungen, dann benannte Abschnitte für jede Freigabe.
[global]
workgroup = WORKGROUP
server string = Mein Linux Server
security = user
map to guest = bad user
# Passwortgeschützte Freigabe (empfohlen):
[daten]
comment = Gemeinsame Daten
path = /srv/samba/daten
browseable = yes
read only = no
valid users = admin
create mask = 0660
directory mask = 0770
# Öffentliche Freigabe -- nur lesen, kein Passwort:
[public]
comment = Oeffentliche Dateien
path = /srv/samba/public
browseable = yes
read only = yes
guest ok = yes Verzeichnisse anlegen und Berechtigungen setzen (aus Modul 11):
# Verzeichnisse erstellen:
sudo mkdir -p /srv/samba/daten /srv/samba/public
# Besitzer setzen:
sudo chown admin:admin /srv/samba/daten
sudo chmod 750 /srv/samba/daten
# Öffentliches Verzeichnis (alle dürfen lesen):
sudo chown nobody:nogroup /srv/samba/public
sudo chmod 755 /srv/samba/public
# Konfiguration prüfen (zeigt Fehler und aktive Einstellungen):
testparm
# SAMBA neu starten:
sudo systemctl restart smbd Die Option guest ok = yes erlaubt Zugriff ohne Anmeldung. Das ist nur für wirklich unkritische Dateien im vertrauenswürdigen Heimnetz vertretbar. Im Büronetz oder bei sensiblen Daten immer valid users mit Passwort nutzen -- sonst kann jeder im Netz auf deine Dateien zugreifen.
SAMBA hat ein eigenes Passwort-System, das vom Linux-Login unabhängig ist. Ein Benutzer muss zunächst als Linux-Benutzer existieren -- dann erhält er ein separates SAMBA-Passwort.
# SAMBA-Passwort für bestehenden Linux-Benutzer setzen:
sudo smbpasswd -a admin
# Du wirst nach dem neuen SAMBA-Passwort gefragt (2x eingeben)
# Benutzer aktivieren (nach smbpasswd -a normalerweise automatisch):
sudo smbpasswd -e admin
# Aktive SAMBA-Benutzer anzeigen:
sudo pdbedit -L
# Verbindungen prüfen (wer ist gerade verbunden?):
sudo smbstatus Das SAMBA-Passwort und das Linux-Login-Passwort sind unabhängig. Wenn du das Linux-Passwort änderst, ändert sich das SAMBA-Passwort nicht automatisch. Du musst beide separat verwalten. Das ist eine häufige Fehlerquelle.
Zugriff von Windows
Unter Windows erreichst du eine SAMBA-Freigabe direkt im Explorer -- kein zusätzliches Programm nötig:
Öffne den Explorer und gib in die Adresszeile ein:
\\192.168.1.10\daten
# Oder mit dem Servernamen (falls DNS funktioniert):
\\mein-server\daten Windows fragt nach Benutzername und Passwort -- gib deine SAMBA-Zugangsdaten ein. Danach kannst du die Freigabe als Netzlaufwerk verbinden (Rechtsklick -- "Netzlaufwerk verbinden").
Zugriff von Linux
Auf einem Linux-Client brauchst du das Paket cifs-utils:
# Einmalig installieren:
sudo apt install cifs-utils
# Einhängepunkt erstellen:
sudo mkdir -p /mnt/samba-daten
# Freigabe temporär einbinden:
sudo mount -t cifs //192.168.1.10/daten /mnt/samba-daten -o username=admin Für dauerhafte Einbindung via /etc/fstab legst du eine Credentials-Datei an:
# Datei /etc/samba/.credentials erstellen:
sudo nano /etc/samba/.credentials
# Inhalt der Datei:
# username=admin
# password=dein-samba-passwort
# Datei nur für root lesbar machen:
sudo chmod 600 /etc/samba/.credentials
sudo chown root:root /etc/samba/.credentials
# Eintrag in /etc/fstab:
# //192.168.1.10/daten /mnt/samba-daten cifs _netdev,credentials=/etc/samba/.credentials,uid=1000 0 0 Trage Passwörter niemals im Klartext in /etc/fstab ein -- die Datei ist für alle Benutzer lesbar (ls -l /etc/fstab zeigt -rw-r--r--). Nutze immer eine separate Credentials-Datei mit Berechtigungen 600 (nur root darf lesen).
| Option | Bedeutung | Typischer Wert |
|---|---|---|
path | Verzeichnis auf dem Server | /srv/samba/name |
browseable | In Netzwerkumgebung sichtbar | yes / no |
read only | Nur lesbar (kein Schreiben) | yes / no |
guest ok | Zugriff ohne Passwort erlaubt | yes / no |
valid users | Erlaubte Benutzer oder Gruppen | admin anna @gruppe |
create mask | Berechtigungen für neue Dateien | 0660 |
directory mask | Berechtigungen für neue Ordner | 0770 |
force user | Alle Zugriffe laufen als dieser Benutzer | www-data |
Die smb.conf hat Hunderte von Optionen. Wenn du eine spezifische Anforderung hast -- z.B. Drucker teilen, Time Machine für Mac, oder Berechtigungen für mehrere Gruppen -- frag eine KI: „Schreibe mir einen smb.conf-Abschnitt für eine Freigabe, auf die nur die Gruppe 'buchhaltung' mit Schreibrecht zugreifen darf, alle anderen Benutzer aber nur lesen können. Server ist Ubuntu 24.04."
SAMBA-Verbindungsprobleme können viele Ursachen haben: Firewall, Berechtigungen, falsches Passwort, falsche Workgroup. Führe sudo smbstatus und sudo journalctl -u smbd -n 50 aus und frag: „Windows findet meine SAMBA-Freigabe nicht. Hier ist die Ausgabe von smbstatus und die letzten SAMBA-Logeinträge -- was könnte falsch sein?"
Du überträgst Dateien sicher auf einen Server und zurück -- der schnellste Weg ohne extra Software. Alle Schritte laufen auf deinem Rechner im SFTP-Prompt.
echo "Hallo vom Laptop" > testdatei.txtsftp benutzer@server-ip. Du solltest den sftp>-Prompt sehen.put testdatei.txt. Prüfe mit ls, ob sie auf dem Server angekommen ist.lmkdir download, dann get testdatei.txt download/.exit. Prüfe lokal mit ls download/, ob die Datei angekommen ist.Lösungshinweise anzeigen
Im SFTP-Prompt zeigt ls den Server-Inhalt, lls den lokalen. Nach put testdatei.txt sollte sie bei ls auf dem Server erscheinen. Der get-Befehl lädt sie in das lokale download/-Verzeichnis.
Fehlermeldung "Couldn't get handle"? Dann fehlt das lokale Zielverzeichnis -- erst lmkdir download ausführen, dann get wiederholen.
Du richtest auf dem Server eine passwortgeschützte SAMBA-Freigabe ein und verbindest dich anschließend vom Client aus. Das Einrichten passiert auf dem Server, das Verbinden auf deinem Rechner.
sudo apt install samba. Prüfe danach den Status: sudo systemctl status smbd. Der Dienst sollte "active (running)" anzeigen.sudo mkdir -p /srv/samba/share, dann sudo chown admin:admin /srv/samba/share (ersetze admin durch deinen Benutzernamen auf dem Server)./etc/samba/smb.conf mit sudo nano /etc/samba/smb.conf und füge am Ende diesen Block ein (Benutzernamen anpassen):[share] path = /srv/samba/share valid users = admin read only = nosudo smbpasswd -a admin. Prüfe die Konfiguration mit testparm. Starte SAMBA neu: sudo systemctl restart smbd.exit um den Server zu verlassensmbclient //server-ip/share -U admin. Gib dein SAMBA-Passwort ein. Bei Erfolg erscheint der smb: \>-Prompt -- tippe ls und dann exit.Lösungshinweise anzeigen
testparm gibt "Loaded services file OK" aus, wenn smb.conf syntaktisch korrekt ist. Beim smbclient-Test erscheint nach dem Passwort ein smb: \>-Prompt, wo du mit ls den Freigabe-Inhalt siehst.
Von Windows erreichst du die Freigabe mit \\server-ip\share im Explorer-Adressfeld. Windows fragt nach Benutzername und Passwort -- trage deine SAMBA-Zugangsdaten ein.
Du bindest ein Server-Verzeichnis so ein, dass es sich wie ein lokaler Ordner anfühlt -- ideal für regelmäßige Arbeit mit Server-Dateien. Alle Schritte laufen auf deinem Rechner.
sudo apt install sshfsmkdir -p ~/mnt/serversshfs benutzer@server-ip:/home/benutzer ~/mnt/serverdf -h, ob das Laufwerk eingebunden ist. Schau dann mit ls ~/mnt/server in den Server-Ordner -- du siehst die Dateien des Servers.echo "Hallo Server" > ~/mnt/server/test.txt. Verbinde dich per SSH und prüfe: ls ~ -- die Datei liegt auf dem Server.fusermount -u ~/mnt/serverLösungshinweise anzeigen
df -h zeigt eine Zeile wie benutzer@192.168.1.10:/home/benutzer ... ~/mnt/server -- das bestätigt, dass der Mount aktiv ist. Dateien, die du dort erstellst, liegen physisch auf dem Server.
Wenn fusermount -u mit "device is busy" scheitert: Stelle sicher, dass du das Verzeichnis nicht mehr im Terminal geöffnet hast. Notfalls hilft sudo umount ~/mnt/server.
Webserver & Reverse Proxy
Ein Webserver liefert Dateien an Browser aus – und ein Reverse Proxy leitet Anfragen an interne Dienste weiter. In diesem Modul lernst du Nginx und Caddy kennen, richtest HTTPS ein und betreibst mehrere Dienste auf einem einzigen Server.
In diesem Modul richtest du einen Webserver ein. Alle Befehle und Konfigurationen laufen auf dem Server – du bist per SSH eingeloggt (Modul 02). Auf deinem eigenen Rechner öffnest du nur den Browser, um das Ergebnis zu testen.
Alle Befehle in diesem Modul laufen auf dem Server – nach dem SSH-Login.
Wenn du eine Adresse im Browser eingibst, passiert folgendes:
- Der Browser fragt DNS: „Welche IP hat
example.com?" – und bekommt eine IP-Adresse zurück. - Der Browser verbindet sich mit dieser IP auf Port 80 (HTTP) oder 443 (HTTPS).
- Der Webserver empfängt die Anfrage, sucht die passende Datei und schickt sie zurück.
- Der Browser stellt die Seite dar.
Ein Reverse Proxy ist wie ein Empfang im Betrieb: Alle Anrufe kommen dort an und werden an die richtige Stelle weitergeleitet. Statt Port 3000 oder 8080 direkt zu öffnen, nimmt der Reverse Proxy alle Anfragen auf Port 80/443 entgegen und reicht sie intern weiter.
| Begriff | Bedeutung | Analogie |
|---|---|---|
| Webserver | Liefert Dateien auf Anfrage aus | Lagerist, der Pakete herausgibt |
| Virtual Host | Mehrere Domains auf einer IP | Mehrere Firmen im gleichen Gebäude |
| Reverse Proxy | Leitet Anfragen an interne Dienste weiter | Empfang, der Anrufe verteilt |
| SSL/TLS | Verschlüsselte HTTPS-Verbindung | Versiegelter Brief statt Postkarte |
| Let's Encrypt | Kostenlose SSL-Zertifikate | Kostenloser Notar |
Bei HTTP sieht jeder im Netzwerk mit, was übertragen wird – Passwörter, Formulardaten, alles im Klartext. HTTPS verschlüsselt die Verbindung. Für echte Domains empfiehlt sich Let's Encrypt mit Certbot (bei Nginx) oder die automatische Zertifikatsverwaltung von Caddy. Im lokalen Testnetz ohne echte Domain kannst du zunächst mit HTTP arbeiten.
Jede Serverantwort enthält einen dreistelligen Statuscode. Du begegnest ihnen in Logs und beim Debuggen ständig.
| Code | Bedeutung | Typische Ursache |
|---|---|---|
200 OK | Alles gut, Datei geliefert | Normaler Seitenaufruf |
301 Moved Permanently | Seite dauerhaft verschoben | HTTP → HTTPS-Weiterleitung |
302 Found | Seite vorübergehend woanders | Login-Weiterleitung |
403 Forbidden | Zugriff verweigert | Falsche Datei-Berechtigungen |
404 Not Found | Datei nicht gefunden | Falscher Pfad, Datei fehlt |
500 Internal Server Error | Fehler im Server/Anwendung | Defekte Konfiguration, PHP-Fehler |
502 Bad Gateway | Upstream-Dienst antwortet nicht | Reverse Proxy, aber Dienst läuft nicht |
503 Service Unavailable | Server überlastet oder in Wartung | Dienst gestartet, noch nicht bereit |
Nginx (sprich: „Engine-X") ist einer der meistgenutzten Webserver weltweit. Er ist schnell, stabil und kann alles: statische Dateien ausliefern, als Reverse Proxy dienen, mehrere Domains auf einem Server betreiben. Die Konfiguration ist etwas technischer als bei Caddy, aber sehr mächtig.
Nginx lässt sich direkt aus den Ubuntu-Paketquellen installieren:
sudo apt update
sudo apt install -y nginx
# Status prüfen:
sudo systemctl status nginx
# Nginx startet automatisch mit dem System:
sudo systemctl enable nginx Nach der Installation läuft Nginx sofort. Öffne http://server-ip im Browser – du siehst die Nginx-Willkommensseite.
Statische Website mit Nginx ausliefern
Nginx legt Websites standardmäßig unter /var/www/ ab. Jede Domain bekommt dort ein eigenes Verzeichnis.
# Verzeichnis anlegen:
sudo mkdir -p /var/www/meinsite
# Einfache HTML-Seite erstellen:
echo '<h1>Hallo vom Webserver</h1>' | sudo tee /var/www/meinsite/index.html
# Berechtigungen setzen – nginx-Prozess läuft als www-data:
sudo chown -R www-data:www-data /var/www/meinsite
sudo chmod -R 755 /var/www/meinsite Nginx läuft unter dem Benutzer www-data. Damit der Server Dateien lesen darf, müssen die Verzeichnisse diesem Benutzer gehören oder für alle lesbar sein. Ein 403-Fehler im Browser bedeutet meistens: falsche Berechtigungen.
Nginx-Virtual-Host konfigurieren
Nginx-Konfigurationen für einzelne Websites heißen Server Blocks. Jede Domain bekommt eine eigene Datei unter /etc/nginx/sites-available/.
server {
listen 80;
server_name meinsite.local 192.168.1.50;
root /var/www/meinsite;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Logs:
access_log /var/log/nginx/meinsite-access.log;
error_log /var/log/nginx/meinsite-error.log;
} Jetzt aktivieren und testen:
# Symlink anlegen – aktiviert die Konfiguration:
sudo ln -s /etc/nginx/sites-available/meinsite /etc/nginx/sites-enabled/
# Konfiguration auf Fehler prüfen:
sudo nginx -t
# Nginx neu laden (ohne Unterbrechung):
sudo systemctl reload nginx Nginx nutzt zwei Verzeichnisse: sites-available enthält alle Konfigurationen (aktive und inaktive). sites-enabled enthält nur Symlinks auf aktive Konfigurationen. Mit ln -s aktivierst du eine Seite – mit rm im sites-enabled-Verzeichnis deaktivierst du sie, ohne die Konfigurationsdatei zu löschen.
Der häufigste Einsatz von Nginx in der Praxis: Als Reverse Proxy vor einem internen Dienst. Zum Beispiel läuft eine Anwendung auf Port 3000 und soll unter einer schönen URL erreichbar sein.
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
} Ohne diese Header sieht die interne Anwendung nur die IP von Nginx (127.0.0.1) – nicht die echte Client-IP. Mit X-Real-IP und X-Forwarded-For gibt Nginx die ursprüngliche Client-IP weiter. Das ist wichtig für Logs, Rate-Limiting und Sicherheitsfilter.
Caddy ist jünger als Nginx, aber in vielen Szenarien die bessere Wahl für Einsteiger. Der größte Vorteil: automatisches HTTPS. Du gibst einen Domain-Namen an – Caddy holt sich das Let's Encrypt-Zertifikat von alleine und erneuert es auch automatisch.
| Merkmal | Nginx | Caddy |
|---|---|---|
| Konfigurationssyntax | Komplex, viele Direktiven | Einfach, fast lesbar wie Prosa |
| Automatisches HTTPS | Nein (Certbot nötig) | Ja, eingebaut |
| Zertifikatserneuerung | Manuell oder Cronjob | Vollautomatisch |
| Paketquellen | Standard-Ubuntu-Repo | Eigenes Repo nötig |
| Verbreitung | Sehr weit verbreitet, viel Doku | Wächst stark, moderne Doku |
| Bester Einsatz | Komplexe Setups, viel Kontrolle | Einfache Setups, schneller Start |
Caddy installieren
Caddy ist nicht im Standard-Ubuntu-Repository – du fügst das offizielle Caddy-Repository hinzu:
# Abhängigkeiten:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
# GPG-Schlüssel herunterladen:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
# Repository eintragen:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
# Caddy installieren:
sudo apt update
sudo apt install -y caddy
# Status prüfen:
sudo systemctl status caddy Das Caddyfile – Konfiguration für Menschen
Die Konfigurationsdatei von Caddy (/etc/caddy/Caddyfile) ist bewusst einfach gehalten. Ein Block pro Domain, klare Direktiven, keine Symlink-Acrobatik.
# Statische Website (automatisches HTTPS bei echter Domain):
example.com {
root * /var/www/example.com
file_server
}
# Reverse Proxy zu einem internen Dienst:
app.example.com {
reverse_proxy localhost:3000
}
# Mehrere Dienste auf einem Server:
grafana.example.com {
reverse_proxy localhost:3001
}
# Lokal testen ohne echte Domain (kein automatisches HTTPS):
:8080 {
root * /var/www/test
file_server
} Nach Änderungen Konfiguration prüfen und neu laden:
# Konfiguration validieren:
caddy validate --config /etc/caddy/Caddyfile
# Caddy neu laden (kein Neustart, keine Downtime):
sudo systemctl reload caddy Sobald du einen echten Domain-Namen im Caddyfile angibst (nicht :8080 oder eine IP), holt Caddy automatisch ein kostenloses Let's Encrypt-Zertifikat. Voraussetzungen: DNS-A-Record zeigt auf den Server, Ports 80 und 443 sind in der Firewall offen (Port 80 braucht Let's Encrypt für die Verifikation).
Bei Nginx musst du HTTPS selbst einrichten. Das Standardwerkzeug dafür ist Certbot – es holt das Let's Encrypt-Zertifikat und trägt es automatisch in die Nginx-Konfiguration ein.
# Certbot installieren:
sudo apt install -y certbot python3-certbot-nginx
# Zertifikat für eine Domain holen und Nginx-Config anpassen:
sudo certbot --nginx -d example.com
# Zertifikat-Erneuerung testen (Certbot richtet Cronjob ein):
sudo certbot renew --dry-run Let's Encrypt muss deinen Server über Port 80 erreichen, um die Domain-Inhaberschaft zu bestätigen. Danach läuft alles über Port 443. Öffne beide Ports in der Firewall – ausführlich erklärt in Modul 14. Kurzform: sudo ufw allow 80/tcp und sudo ufw allow 443/tcp.
Webserver schreiben in zwei Log-Dateien: Access-Log (jede Anfrage) und Error-Log (Fehler und Warnungen). Diese Logs sind dein wichtigstes Werkzeug beim Debuggen.
# Nginx-Logs:
sudo tail -f /var/log/nginx/access.log # Live-Ansicht Anfragen
sudo tail -f /var/log/nginx/error.log # Live-Ansicht Fehler
# Caddy-Logs (über systemd journal):
sudo journalctl -u caddy -f # Live-Ansicht
sudo journalctl -u caddy -n 50 # Letzte 50 Zeilen
# Nur Fehler im Nginx-Error-Log zählen:
grep -c "error" /var/log/nginx/error.log Eine typische Zeile im Nginx-Access-Log sieht so aus – so liest du sie:
192.168.1.5 - - [15/Mar/2026:14:23:01 +0100] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0" | Teil der Log-Zeile | Bedeutung |
|---|---|
192.168.1.5 | IP-Adresse des Besuchers |
15/Mar/2026:14:23:01 | Datum und Uhrzeit der Anfrage |
GET /index.html HTTP/1.1 | Angeforderte Datei und Methode |
200 | HTTP-Statuscode (200 = OK) |
1234 | Größe der Antwort in Bytes |
Nginx-Konfigurationen können schnell komplex werden – besonders für Reverse Proxy mit SSL, WebSocket-Unterstützung oder speziellen HTTP-Headern. Beschreibe deinen Anwendungsfall einer KI: „Erstelle mir eine vollständige Nginx-Server-Block-Konfiguration für Nextcloud unter nextcloud.meinserver.de. Sie läuft intern auf Port 8080. HTTPS soll über Certbot eingerichtet sein."
Damit der Webserver von außen erreichbar ist, müssen die Ports 80 (HTTP) und 443 (HTTPS) in der Firewall freigegeben sein. Die Firewall-Administration ist das Thema von Modul 14 – hier die Kurzversion für dieses Modul:
# HTTP und HTTPS freigeben:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Alternativ – vordefiniertes Nginx-Profil nutzen:
sudo ufw allow 'Nginx Full'
# Status prüfen:
sudo ufw status Nach der Nginx-Installation registriert sich Nginx automatisch als ufw-Applikation mit drei Profilen: Nginx HTTP (nur Port 80), Nginx HTTPS (nur Port 443), Nginx Full (beide). Mit sudo ufw app list siehst du alle verfügbaren Profile.
Caddy hat viele Direktiven für spezielle Szenarien: WebSocket-Support, Authentifizierung, Komprimierung, Health Checks. Wenn du einen konkreten Dienst einrichten willst, frag eine KI: „Schreibe mir ein vollständiges Caddyfile für Gitea unter git.meinserver.de mit Reverse Proxy auf Port 3000, aktiviertem gzip und den nötigen Sicherheits-Headern."
Du installierst Nginx und richtest eine einfache statische Website ein – die Grundlage für alle weiteren Webprojekte.
sudo apt update && sudo apt install -y nginx. Prüfe danach den Status: sudo systemctl status nginx – der Dienst sollte als „active (running)" angezeigt werden.sudo mkdir -p /var/www/meinsite, dann echo '<h1>Hallo Welt</h1>' | sudo tee /var/www/meinsite/index.html.sudo chown -R www-data:www-data /var/www/meinsite. Ohne das bekommst du einen 403-Fehler im Browser.sudo nano /etc/nginx/sites-available/meinsite. Schreibe einen Server-Block, der auf Port 80 lauscht und Dateien aus /var/www/meinsite ausliefert.sudo ln -s /etc/nginx/sites-available/meinsite /etc/nginx/sites-enabled/. Prüfe die Konfiguration: sudo nginx -t. Lade Nginx neu: sudo systemctl reload nginx.http://server-ip im Browser deines Rechners. Du solltest „Hallo Welt" sehen.Lösungshinweise anzeigen
Ein minimaler Server-Block für diese Aufgabe: server { listen 80; server_name _; root /var/www/meinsite; index index.html; location / { try_files $uri $uri/ =404; } }. server_name _ ist ein Platzhalter, der auf alle Hostnamen passt.
Wenn der Browser nichts zeigt: Prüfe mit sudo journalctl -u nginx -n 20 ob Nginx läuft. Bei einem 403: Berechtigungen mit ls -la /var/www/meinsite kontrollieren.
Du leitest Anfragen an einen internen Dienst weiter – das ist der häufigste Einsatz von Nginx in der Praxis.
python3 -m http.server 3000 &. Prüfe ob er läuft: curl http://localhost:3000.sudo nano /etc/nginx/sites-available/proxy. Konfiguriere einen Server-Block auf Port 8080, der alle Anfragen mit proxy_pass an http://127.0.0.1:3000 weiterleitet.sudo ln -s /etc/nginx/sites-available/proxy /etc/nginx/sites-enabled/ && sudo nginx -t && sudo systemctl reload nginx.curl http://localhost:8080. Du solltest die Antwort des Python-HTTP-Servers sehen.sudo tail -f /var/log/nginx/access.log. Öffne in einem zweiten Terminal nochmal curl http://localhost:8080 und lies den neuen Log-Eintrag.Lösungshinweise anzeigen
Der Server-Block braucht mindestens: server { listen 8080; location / { proxy_pass http://127.0.0.1:3000; } }. Im Access-Log erscheint jede Anfrage mit Statuscode 200 und der Antwortgröße.
Wenn curl http://localhost:8080 einen 502-Fehler gibt: Der Python-Server läuft nicht mehr. Prüfe mit jobs oder ps aux | grep python3.
Du installierst Caddy als moderne Alternative zu Nginx und lernst den Unterschied in der Konfiguration kennen.
sudo systemctl stop nginx.sudo apt install -y caddy). Prüfe mit sudo systemctl status caddy./etc/caddy/Caddyfile an: Konfiguriere Caddy auf Port 8080 mit root * /var/www/meinsite und file_server. Nutze die :8080 { ... }-Syntax.caddy validate --config /etc/caddy/Caddyfile && sudo systemctl restart caddy.http://server-ip:8080 im Browser. Beobachte gleichzeitig die Logs: sudo journalctl -u caddy -f.Lösungshinweise anzeigen
Das Caddyfile für diese Aufgabe besteht aus vier Zeilen: :8080 {, dann root * /var/www/meinsite, dann file_server, dann }. Die geschwungenen Klammern müssen auf eigenen Zeilen stehen – Caddy ist da streng.
Vergleiche den Aufwand mit der Nginx-Konfiguration aus Aufgabe 13.1: Caddy braucht keine sites-available/sites-enabled-Struktur, keinen Symlink-Befehl. Sobald du eine echte Domain angibst, kommt HTTPS automatisch dazu.
Du lernst, Webserver-Logs zu lesen und gezielt nach Fehlern zu suchen – eine grundlegende Fähigkeit im Server-Betrieb.
sudo systemctl start nginx. Öffne das Access-Log in Echtzeit in einem Terminal: sudo tail -f /var/log/nginx/access.log.curl http://localhost/ (sollte 200 liefern) und curl http://localhost/nichtvorhanden (sollte 404 liefern).sudo grep "error" /var/log/nginx/error.log | tail -20.sudo chmod 000 /var/www/meinsite/index.html, dann rufe die Seite auf. Beobachte den 403-Fehler im Access-Log und den Hinweis im Error-Log.sudo chmod 644 /var/www/meinsite/index.html. Prüfe ob die Seite wieder erreichbar ist.Lösungshinweise anzeigen
Der 403-Fehler erscheint sowohl im Access-Log (Statuscode 403 in der Log-Zeile) als auch im Error-Log mit einem Hinweis wie permission denied und dem Dateipfad. So findest du Berechtigungsprobleme im echten Betrieb schnell.
Merke: 404 = Datei fehlt, 403 = Datei existiert aber darf nicht gelesen werden. Beide sehen im Browser ähnlich aus – die Logs verraten den Unterschied sofort.
Firewall-Administration
Ein Server ohne Firewall ist wie ein Haus ohne Türschloss -- jeder kann rein. In diesem Modul lernst du, mit ufw eine Firewall einzurichten, Ports gezielt zu öffnen und zu schließen, und Regeln sicher zu verwalten.
Firewall-Befehle werden direkt auf dem Server ausgeführt -- du brauchst dafür sudo-Rechte. Verbinde dich zuerst per SSH auf deinen Server (Modul 02), und führe dann alle Befehle aus diesem Modul dort aus.
Du bist per SSH eingeloggt und gibst alle Befehle direkt auf dem Server ein. Dein lokaler Rechner bleibt im Hintergrund.
Eine Firewall entscheidet, welche Netzwerkverbindungen zu deinem Server erlaubt sind und welche geblockt werden. Sie arbeitet mit Regeln: Für jeden eingehenden Datenpaket wird geprüft, ob er durchkommt oder blockiert wird.
Stell dir die Firewall als Türsteher vor: Er hat eine Gästeliste. Port 22 (SSH) -- rein. Port 443 (HTTPS) -- rein. Unbekannter Port 3306 (Datenbank) -- draußen bleiben. Was nicht auf der Liste steht, kommt nicht rein.
Ohne Firewall ist jeder Port erreichbar, auf dem ein Dienst lauscht. Datenbanken, Admin-Panels oder interne Dienste wären dann direkt aus dem Internet zugänglich. Eine Firewall schränkt den Zugriff auf das Nötigste ein -- das ist Grundlage jeder Sicherheitsstrategie.
| Ebene | Werkzeug | Rolle |
|---|---|---|
| Kernel | nftables / iptables | Die eigentliche Firewall im Linux-Kern |
| Verwaltung | ufw | Einfaches Frontend für iptables/nftables |
| Anzeige | ufw status | Aktuelle Regeln im Überblick |
ufw steht für "Uncomplicated Firewall" -- zu Recht. Was mit iptables viele Zeilen braucht, geht mit ufw in einem Befehl. Auf Ubuntu und Debian ist ufw meistens bereits installiert, aber noch nicht aktiv.
Wenn du ufw aktivierst, ohne vorher SSH zu erlauben, sperrst du dich sofort selbst aus. Auf einem Remote-Server ohne lokalen Bildschirmzugang gibt es dann keinen Weg zurück. Die Reihenfolge ist zwingend: 1. SSH erlauben, 2. ufw aktivieren. Kein einziger Schritt darf umgedreht werden.
Installiere ufw, falls es noch nicht vorhanden ist, und richte die Grundkonfiguration ein.
# ufw installieren (auf Debian/Ubuntu meist schon dabei):
sudo apt install ufw
# Aktuellen Status prüfen:
sudo ufw status
# Standard-Richtlinien setzen -- IMMER als erstes:
sudo ufw default deny incoming
sudo ufw default allow outgoing
# SSH erlauben -- VOR dem Aktivieren!
sudo ufw allow ssh
# ufw aktivieren:
sudo ufw enable
# Status mit allen Regeln anzeigen:
sudo ufw status verbose Nach ufw enable fragt ufw noch einmal nach, ob du sicher bist. Bestätige mit y. Deine laufende SSH-Verbindung bleibt dabei erhalten -- nur neue Regeln greifen ab sofort.
deny incoming bedeutet: Alles, was von außen kommt, ist gesperrt -- außer du erlaubst es explizit. allow outgoing bedeutet: Dein Server darf selbst Verbindungen nach draußen aufbauen (z.B. für apt update). Das ist die sichere Standardeinstellung.
Mit ufw allow öffnest du einen Port, mit ufw deny sperrst du ihn explizit. Du kannst Ports entweder über ihren Namen (z.B. ssh, http) oder direkt über die Nummer (z.B. 22/tcp) ansprechen.
# Dienste nach Name erlauben (ufw kennt die Standard-Ports):
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
# Ports direkt nach Nummer erlauben:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8080/tcp
# Nginx mit HTTP und HTTPS in einem Schritt:
sudo ufw allow 'Nginx Full'
# Port explizit sperren:
sudo ufw deny 3306/tcp
# Port-Bereich erlauben:
sudo ufw allow 6000:6100/tcp Zugriff auf bestimmte IP-Adressen einschränken
Manchmal soll ein Port nur für bestimmte Rechner erreichbar sein -- z.B. der Datenbank-Port nur aus dem internen Netzwerk. Das erreichst du mit from-Regeln.
# Nur eine bestimmte IP darf auf Port 22 (SSH) zugreifen:
sudo ufw allow from 192.168.1.100 to any port 22
# Ganzes internes Subnetz darf auf den Server zugreifen:
sudo ufw allow from 192.168.1.0/24
# Nur internes Netz darf auf MySQL (Port 3306) zugreifen:
sudo ufw allow from 192.168.1.0/24 to any port 3306
# Rate Limiting für SSH (max. 6 Versuche pro 30 Sekunden):
sudo ufw limit ssh Um den Überblick zu behalten, zeigst du die Regeln nummeriert an. Das macht das Löschen einzelner Regeln einfach -- du gibst einfach die Nummer an.
# Alle Regeln nummeriert anzeigen:
sudo ufw status numbered
# Regel nach Nummer löschen (Nummer aus obigem Befehl):
sudo ufw delete 3
# Regel nach Name/Port löschen:
sudo ufw delete allow 8080/tcp
sudo ufw delete allow http
# ufw vorübergehend deaktivieren (ohne Regeln zu löschen):
sudo ufw disable
# ufw wieder aktivieren:
sudo ufw enable sudo ufw --force reset setzt die Firewall auf den Werkszustand zurück -- alle Regeln sind weg, und die Firewall ist deaktiviert. Auf einem Remote-Server bedeutet das: alle Ports offen, keine Sicherheit mehr. Verwende diesen Befehl nur, wenn du weißt was du tust, und richte danach sofort neue Regeln ein.
Häufige Ports -- Referenz
Merke: Weniger ist mehr -- öffne nur, was wirklich gebraucht wird.
| Port | Protokoll | Dienst | ufw-Empfehlung |
|---|---|---|---|
| 22 | TCP | SSH | allow ssh + limit ssh |
| 80 | TCP | HTTP | allow http |
| 443 | TCP | HTTPS | allow https |
| 25 | TCP | SMTP (Mail) | Nur wenn Mailserver betrieben |
| 53 | TCP/UDP | DNS | Nur wenn DNS-Server (Modul 15) |
| 3306 | TCP | MySQL | Niemals offen! Nur lokal / Tunnel |
| 5432 | TCP | PostgreSQL | Niemals offen! Nur lokal / Tunnel |
| 3389 | TCP | RDP (Remote Desktop) | Nur bei Bedarf, IP einschränken |
| 8080 | TCP | Anwendungsserver | Nur wenn benötigt |
MySQL (3306), PostgreSQL (5432) und Redis (6379) sollten grundsätzlich nicht über die Firewall freigegeben werden. Sie sind nur lokal oder über einen SSH-Tunnel (Modul 02) erreichbar. Wer diese Ports nach außen öffnet, riskiert automatisierte Angriffe innerhalb von Minuten.
Übersicht aller ufw-Befehle
| Befehl | Bedeutung |
|---|---|
sudo ufw status | Status und Regeln anzeigen |
sudo ufw status verbose | Status mit Details und Policies |
sudo ufw status numbered | Regeln mit Nummern anzeigen |
sudo ufw enable | Firewall aktivieren |
sudo ufw disable | Firewall deaktivieren |
sudo ufw allow ssh | SSH erlauben (Port 22) |
sudo ufw deny 3306/tcp | Port explizit sperren |
sudo ufw delete 3 | Regel Nr. 3 löschen |
sudo ufw limit ssh | Rate Limiting für SSH |
sudo ufw logging on | Logging aktivieren |
ufw kann geblockte Verbindungen protokollieren. So siehst du, wer versucht auf deinen Server zuzugreifen -- und auf welchen Port.
# Logging aktivieren:
sudo ufw logging on
# Logging-Stufe anpassen (low = wenig, high = viel):
sudo ufw logging medium
# Stufen: off, low, medium, high, full
# Geblockte Verbindungen in Echtzeit beobachten:
sudo journalctl -k --grep "UFW BLOCK" -f
# Oder im syslog nachschauen:
sudo grep "UFW BLOCK" /var/log/syslog | tail -20 Ein typischer Log-Eintrag sieht so aus:
# UFW BLOCK IN=eth0 SRC=185.220.101.34 DST=192.168.1.10 PROTO=TCP DPT=3306
# Das bedeutet: Jemand von 185.220.101.34 wollte auf Port 3306 (MySQL) -- geblockt! Wenn du nicht sicher bist, welche Ports offen oder riskant sind, kombiniere sudo ufw status verbose mit ss -tlnp (zeigt alle Ports, auf denen ein Dienst lauscht). Kopiere beide Ausgaben und frag: „Hier sind meine Firewall-Regeln und alle lauschenden Dienste meines Servers. Welche Ports sollten laut Best Practice geschlossen werden und warum?"
ufw ist nur ein bequemes Werkzeug vorne -- im Hintergrund arbeitet immer iptables (oder das modernere nftables). Du wirst iptables selten direkt brauchen, aber ein Grundverständnis hilft.
Stell dir iptables wie den Motor vor und ufw wie das Lenkrad: Du steuerst mit ufw, aber der Motor (iptables) macht die eigentliche Arbeit. Wenn du mal tief in die Regeln schauen musst, hilft iptables weiter.
# Aktuelle Regeln anzeigen (zeigt auch was ufw eingerichtet hat):
sudo iptables -L -n --line-numbers
# Regeln mit Paket-Statistiken:
sudo iptables -L -n -v
# Alle Regeln komplett exportieren:
sudo iptables-save Verwende für den Alltag immer ufw. Direkte iptables-Befehle werden nach einem Neustart nicht gespeichert (außer mit extra Konfiguration). ufw dagegen speichert alle Regeln persistent. iptables direkt nutzt du nur zur Diagnose oder wenn du sehr spezifische Regeln brauchst, die ufw nicht unterstützt.
ufw-Fehler wie "ERROR: Could not load logging rules" oder unerwartetes Verhalten nach einem Neustart sind manchmal schwer zu diagnostizieren. Führe sudo ufw status verbose aus, kopiere die Ausgabe und frag: „Meine ufw-Konfiguration zeigt diesen Status. Ist das korrekt für einen Webserver, der nur SSH, HTTP und HTTPS erlauben soll? Was muss ich anpassen?"
Du richtest die Firewall-Grundkonfiguration auf deinem Server ein. Das ist der erste Schritt zu einem sicheren System -- und die Reihenfolge ist entscheidend.
sudo ufw status. Ist sie aktiv oder inaktiv?sudo ufw default deny incoming und sudo ufw default allow outgoing.sudo ufw allow ssh. Prüfe mit sudo ufw show added, ob die Regel vorhanden ist.sudo ufw enable. Bestätige mit y. Prüfe danach mit sudo ufw status verbose.sudo ufw allow http und sudo ufw allow https. Zeige den finalen Status mit sudo ufw status numbered.Lösungshinweise anzeigen
sudo ufw status verbose sollte anzeigen: Status: active, Default: deny (incoming), allow (outgoing). In der Regelliste stehen SSH (22), HTTP (80) und HTTPS (443).
Teste, ob du noch per SSH erreichbar bist, indem du ein zweites Terminal öffnest und dich erneut verbindest. Wenn das klappt, ist alles korrekt eingerichtet.
Du lernst, einzelne Regeln hinzuzufügen, zu prüfen und wieder zu entfernen -- ohne die gesamte Firewall zu verändern.
sudo ufw allow 9999/tcp. Zeige die Regeln nummeriert an: sudo ufw status numbered. Notiere die Nummer der 9999/tcp-Regel.sudo ufw allow from 192.168.1.0/24 to any port 22. (Passe das Subnetz an dein Netzwerk an.)sudo ufw limit ssh. Das schützt vor automatisierten Brute-Force-Angriffen.sudo ufw delete [NUMMER]. Bestätige und prüfe mit sudo ufw status numbered, ob Port 9999 verschwunden ist.ss -tlnp. Welche Ports lauschen, aber sind in ufw nicht geöffnet?Lösungshinweise anzeigen
Nach dem Löschen darf Port 9999 nicht mehr in sudo ufw status erscheinen. ss -tlnp zeigt alle Ports, auf denen ein Dienst lauscht -- das können mehr sein als in ufw geöffnet, weil ufw nur eingehende Verbindungen filtert. Dienste, die nur auf 127.0.0.1 lauschen, sind ohnehin nur lokal erreichbar.
Rate Limiting (limit) erlaubt maximal 6 Verbindungsversuche von einer IP innerhalb von 30 Sekunden. Danach wird diese IP kurzzeitig geblockt.
Du aktivierst das ufw-Logging und lernst, geblockte Verbindungen zu erkennen und zu interpretieren.
sudo ufw logging on. Setze danach die Stufe: sudo ufw logging medium.sudo ufw status verbose. In der Ausgabe sollte "Logging: on (medium)" erscheinen.sudo grep "UFW BLOCK" /var/log/syslog | tail -20. Gibt es Einträge?SRC=? Welcher Port wurde versucht (DPT=)? Welches Interface (IN=)?Lösungshinweise anzeigen
Ein typischer Eintrag sieht so aus: UFW BLOCK IN=eth0 SRC=185.220.101.34 DST=10.0.0.1 PROTO=TCP DPT=22. Das bedeutet: Eine IP hat versucht, auf SSH (Port 22) zuzugreifen -- und wurde geblockt. SRC ist die Quell-IP des Angreifers, DPT der Ziel-Port.
Wenn keine Einträge erscheinen, ist das gut -- dein Server wird gerade nicht angegriffen. Auf öffentlich erreichbaren Servern sind SSH-Scan-Versuche jedoch innerhalb weniger Minuten nach der Inbetriebnahme normal.
DNS-Server mit dnsmasq
Statt IP-Adressen auswendig zu lernen, gibst du deinen Netzgeräten sprechende Namen wie nas.local oder drucker.local. dnsmasq ist dabei dein Werkzeug: klein, einfach und auf jedem Debian/Ubuntu-Server zu Hause.
Alle Befehle in diesem Modul laufen auf dem Server – du bist bereits per SSH eingeloggt (Modul 02). Dein eigener Rechner ist nicht beteiligt.
Alle Befehle in diesem Modul laufen auf dem Server – du bist bereits per SSH eingeloggt.
DNS steht für Domain Name System – das Telefonbuch des Internets. Du tippst google.com und dein Rechner fragt im Hintergrund: „Welche IP-Adresse steckt dahinter?" Ein DNS-Server antwortet mit z.B. 142.250.185.46. Aus Modul 01 kennst du bereits IP-Adressen und die Grundfunktion von DNS.
Im lokalen Netz läuft das genauso – nur dass es hier um deine eigenen Geräte geht. Ohne lokalen DNS-Server musst du für jeden Dienst die IP-Adresse kennen. Mit dnsmasq vergibst du Namen:
| Ohne DNS | Mit dnsmasq |
|---|---|
192.168.1.20 | nas.local |
192.168.1.30 | drucker.local |
192.168.1.50 | kamera1.local |
192.168.1.10 | server.local |
Ändert sich eine IP, trägst du sie einmal nach – fertig. Alle Geräte im Netz, die deinen dnsmasq nutzen, bekommen die Änderung automatisch.
dnsmasq vereint drei Funktionen in einem Dienst: DNS-Cache (Anfragen zwischenspeichern, schnellere Wiederholung), lokales DNS (eigene Namen für deine Geräte) und optional DHCP (automatische IP-Vergabe im Netz). Du kannst jede Funktion einzeln aktivieren.
Bevor du dnsmasq installierst, prüfst du ob Ubuntu bereits einen DNS-Dienst auf Port 53 betreibt. Ubuntu nutzt standardmäßig systemd-resolved – das belegt denselben Port und verhindert, dass dnsmasq startet.
Zuerst prüfst du, ob Port 53 belegt ist:
ss -tlnp | grep :53
# Wenn eine Zeile erscheint: Port 53 ist belegt (oft systemd-resolved)
# Wenn keine Ausgabe: Port 53 ist frei → direkt zu Installation springen Falls systemd-resolved läuft, deaktivierst du es zuerst:
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
# Temporären DNS-Server eintragen, damit der Server noch Namen auflösen kann:
sudo rm /etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf Jetzt installierst du dnsmasq:
sudo apt update
sudo apt install dnsmasq
sudo systemctl status dnsmasq
# Sollte "active (running)" zeigen
ss -tlnp | grep :53
# Jetzt sollte dnsmasq auf Port 53 lauschen Wenn dnsmasq als DNS-Server für alle Geräte im Netz konfiguriert ist und der Dienst abstürzt oder falsch konfiguriert wird, verliert jedes Gerät im Netz die Namensauflösung. Webseiten laden nicht mehr, E-Mail funktioniert nicht, interne Dienste sind nicht erreichbar. Teste Konfigurationsänderungen immer mit dnsmasq --test bevor du den Dienst neustartest.
Die Konfigurationsdateien liegen in /etc/dnsmasq.conf (Hauptdatei, sehr lang und kommentiert) und in /etc/dnsmasq.d/ (eigene Dateien, werden automatisch eingelesen). Lege deine Einstellungen immer in einer eigenen Datei unter /etc/dnsmasq.d/ ab – so bleibt die Original-Datei unangetastet.
Erstelle deine eigene Konfigurationsdatei:
sudo nano /etc/dnsmasq.d/meinetz.conf Füge folgenden Inhalt ein (passe die Netzwerkschnittstelle an – eth0 oder ens3 usw.):
# Welche Netzwerkschnittstelle dnsmasq nutzen soll:
interface=eth0
# Nur auf dieser Schnittstelle lauschen (Sicherheit):
bind-interfaces
# Upstream-DNS-Server – wohin externe Anfragen gehen:
server=1.1.1.1
server=8.8.8.8
# Lokale Domain – .local-Anfragen nicht nach außen schicken:
domain=local
local=/local/
# DNS-Cache-Größe (Standard: 150, höherer Wert = schneller):
cache-size=1000
# Logs für Debugging (optional, kann viel schreiben):
log-queries Konfiguration prüfen und dnsmasq neu starten:
# Syntax prüfen – IMMER vor dem Neustart:
dnsmasq --test
# Ausgabe bei Erfolg: "dnsmasq: syntax check OK."
sudo systemctl restart dnsmasq
sudo systemctl status dnsmasq | Option | Bedeutung | Beispiel |
|---|---|---|
interface | Schnittstelle, auf der dnsmasq lauscht | interface=eth0 |
bind-interfaces | Nur auf angegebene Schnittstellen binden | kein Wert nötig |
server | Upstream-DNS-Server für externe Anfragen | server=1.1.1.1 |
domain | Lokale Domain-Endung | domain=local |
local | Diese Domain nicht nach außen weiterleiten | local=/local/ |
cache-size | Anzahl gecachter DNS-Einträge | cache-size=1000 |
log-queries | Alle DNS-Anfragen ins Log schreiben | kein Wert nötig |
dnsmasq liest automatisch aus /etc/hosts – du trägst dort IP und Name ein und dnsmasq liefert die Antwort. Das ist der einfachste Weg für statische Geräte in deinem Netz.
Trage deine Netzgeräte in /etc/hosts ein:
sudo nano /etc/hosts Füge deine Geräte unterhalb der bestehenden Einträge ein:
# Lokale Netzgeräte:
192.168.1.1 router.local router
192.168.1.10 server.local server
192.168.1.20 nas.local nas
192.168.1.30 drucker.local drucker
192.168.1.50 kamera1.local kamera1 Nach der Änderung reicht ein Reload – bestehende Verbindungen bleiben erhalten:
sudo systemctl reload dnsmasq
# Auflösung testen:
dig nas.local @127.0.0.1
# Im ANSWER SECTION siehst du die zugehörige IP Du kannst Hostnamen auch direkt in einer dnsmasq-Konfigurationsdatei eintragen. Füge in /etc/dnsmasq.d/hosts.conf ein: address=/nas.local/192.168.1.20. Nützlich für Wildcards (address=/*.intern.local/192.168.1.10) oder wenn du viele Einträge nach Schema verwalten willst.
Mit dig und nslookup fragst du deinen dnsmasq direkt ab – ohne dass andere Systemeinstellungen dazwischenfunken. Du kannst den DNS-Server mit @127.0.0.1 direkt ansprechen.
# Lokalen Namen auflösen:
dig nas.local @127.0.0.1
# Im "ANSWER SECTION" siehst du die IP
# Externe Domain auflösen (testet Upstream-Weiterleitung):
dig google.com @127.0.0.1
# Zweite Anfrage sollte "Query time: 0 msec" zeigen (Cache)
dig google.com @127.0.0.1
# Alternativ mit nslookup:
nslookup nas.local 127.0.0.1
# Reverse Lookup – IP zu Name:
dig -x 192.168.1.20 @127.0.0.1
# DNS-Anfragen live beobachten (mit log-queries in der Konfiguration):
sudo journalctl -u dnsmasq -f
# Strg+C zum Beenden dnsmasq gibt bei Fehlern oft kryptische Meldungen aus – besonders beim Start. Kopiere die Fehlermeldung aus sudo journalctl -u dnsmasq -n 30 und frag: „Ich bekomme diesen dnsmasq-Fehler auf Ubuntu 24.04. Was bedeutet er und wie behebe ich ihn?"
Alle Anfragen, die dnsmasq nicht selbst beantworten kann (alles außer deinen lokalen Namen), leitet er an einen Upstream-DNS-Server weiter. Das sind meist öffentliche Server wie Cloudflare (1.1.1.1) oder Google (8.8.8.8). Du kannst bestimmte Domains auch gezielt an andere Server weiterleiten – das nennt sich Split-DNS:
# Anfragen für eine Firmendomain an den Firmen-DNS-Server:
server=/firma.intern/10.0.0.1
# Anfragen für .local NICHT nach außen weiterleiten:
local=/local/
# Alle anderen Anfragen an Cloudflare:
server=1.1.1.1
# Domain komplett sperren (leere Antwort statt IP):
address=/werbung-example.com/0.0.0.0 dnsmasq kann auch IP-Adressen automatisch verteilen – als Ersatz für die DHCP-Funktion deines Routers. Der Vorteil: dnsmasq kennt dann automatisch alle Gerätenamen im Netz und trägt sie in seine DNS-Datenbank ein.
# IP-Bereich für automatische Vergabe (von .100 bis .200, Lease-Zeit 24h):
dhcp-range=192.168.1.100,192.168.1.200,24h
# Standard-Gateway mitteilen (meist der Router):
dhcp-option=3,192.168.1.1
# DNS-Server mitteilen – dieser Server selbst:
dhcp-option=6,192.168.1.10
# Feste IP für ein bestimmtes Gerät (per MAC-Adresse):
dhcp-host=aa:bb:cc:dd:ee:ff,drucker.local,192.168.1.30,infinite Aktuelle DHCP-Leases anzeigen:
cat /var/lib/misc/dnsmasq.leases
# Zeigt: Ablaufzeit, MAC-Adresse, vergebene IP, Hostname Wenn dnsmasq DHCP aktiviert ist, muss der DHCP-Server auf dem Router ausgeschaltet werden. Zwei DHCP-Server im selben Netz führen zu Konflikten: Geräte bekommen zufällig vom falschen Server eine IP – mit falschen DNS-Einstellungen. Schalte erst DHCP auf dem Router ab, dann aktiviere es in dnsmasq.
Linux-Clients auf dnsmasq umstellen
Damit andere Geräte im Netz die lokalen Namen auflösen können, müssen sie deinen Server als DNS-Server kennen. Auf Linux-Clients trägst du die IP deines Servers in Netplan ein (Modul 05):
# Server nutzt seinen eigenen dnsmasq:
echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf
# Testen – lokaler Name sollte jetzt ohne @127.0.0.1 klappen:
ping -c 2 nas.local Trage die IP des dnsmasq-Servers in die Netplan-Konfiguration ein:
network:
version: 2
ethernets:
eth0:
dhcp4: true
nameservers:
addresses: [192.168.1.10] Konfiguration anwenden:
sudo netplan apply
# Testen: lokaler Name sollte jetzt auflösbar sein
nslookup nas.local
# Server: 192.168.1.10 ← zeigt, dass dnsmasq-Server genutzt wird dnsmasq hat dutzende Optionen und die Kombination DNS + DHCP kann schnell komplex werden. Beschreibe deinen Anwendungsfall und lass dir eine fertige Konfiguration erstellen: „Ich möchte dnsmasq als DNS- und DHCP-Server für mein Heimnetz einrichten. Netz: 192.168.1.0/24, Router: 192.168.1.1, dnsmasq-Server: 192.168.1.10, DHCP-Bereich: .100 bis .200. Schreibe mir alle nötigen Konfigurationsdateien."
Du installierst dnsmasq und testest, ob die DNS-Auflösung und der Cache funktionieren.
ss -tlnp | grep :53. Falls systemd-resolved läuft, deaktiviere es gemäß Modulanleitung und setze /etc/resolv.conf auf nameserver 1.1.1.1.sudo apt install dnsmasq. Prüfe danach den Status: sudo systemctl status dnsmasq. Du solltest "active (running)" sehen.dig google.com @127.0.0.1. Notiere dir die Zahl hinter "Query time:".dig google.com @127.0.0.1. Ist die Query time jetzt 0? Das ist der Cache in Aktion.sudo journalctl -u dnsmasq -n 20. Du siehst die weitergeleiteten und gecachten Anfragen.Lösungshinweise anzeigen
Die erste dig-Anfrage zeigt typischerweise 20–50 ms (Anfrage geht bis 1.1.1.1 und zurück). Die zweite Anfrage zeigt "Query time: 0 msec" – der Eintrag kommt aus dem lokalen Cache. Das ist der Hauptvorteil eines DNS-Caches.
Wenn dnsmasq nicht startet: sudo journalctl -u dnsmasq -n 30 zeigt die Fehlermeldung. Häufig ist Port 53 noch von systemd-resolved belegt.
Du gibst deinen Netzgeräten sprechende Namen und testest, ob dnsmasq sie korrekt auflöst.
/etc/hosts mit sudo nano /etc/hosts und trage deinen Server ein, z.B.: 192.168.1.10 meinserver.local meinserver. Trage noch ein weiteres Gerät aus deinem Netz ein.sudo systemctl reload dnsmasq. Ein Reload reicht – kein voller Neustart nötig, bestehende Verbindungen bleiben erhalten.dig meinserver.local @127.0.0.1. Im Abschnitt "ANSWER SECTION" sollte deine IP erscheinen.dig -x 192.168.1.10 @127.0.0.1. dnsmasq sollte "meinserver.local" zurückgeben.echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf. Teste danach: ping -c 2 meinserver.local.Lösungshinweise anzeigen
dig meinserver.local @127.0.0.1 sollte im ANSWER SECTION eine Zeile zeigen: meinserver.local. 0 IN A 192.168.1.10. Die "0" ist die TTL – lokale Einträge aus /etc/hosts haben keine Ablaufzeit.
Reverse Lookups funktionieren bei dnsmasq automatisch für alle Einträge aus /etc/hosts. Nach dem Eintragen von nameserver 127.0.0.1 in /etc/resolv.conf klappt auch ping meinserver.local ohne das @127.0.0.1.
Du legst eine saubere eigene Konfiguration an und beobachtest dnsmasq live beim Arbeiten.
/etc/dnsmasq.d/meinetz.conf mit sudo nano /etc/dnsmasq.d/meinetz.conf. Trage ein: deine Netzwerkschnittstelle (interface=eth0 oder den richtigen Namen), bind-interfaces, zwei Upstream-Server (server=1.1.1.1 und server=8.8.8.8), cache-size=1000 und log-queries.dnsmasq --test. Du solltest "dnsmasq: syntax check OK." lesen. Bei Fehlern: Konfiguration korrigieren und nochmal prüfen.sudo systemctl restart dnsmasq. Prüfe den Status: sudo systemctl status dnsmasq.sudo journalctl -u dnsmasq -f. Lass dieses Terminal offen.dig github.com @127.0.0.1, dig ubuntu.com @127.0.0.1, dann dieselben nochmals. Beobachte im Log wie "forwarded" und "cached" wechseln.Lösungshinweise anzeigen
dnsmasq --test gibt "dnsmasq: syntax check OK." aus, wenn die Konfiguration syntaktisch korrekt ist. Im Live-Log siehst du bei der ersten Anfrage "forwarded github.com to 1.1.1.1" – die Anfrage geht nach außen. Bei der zweiten Anfrage erscheint "cached github.com is ..." – der Wert kommt aus dem Cache.
Alle Dateien unter /etc/dnsmasq.d/ werden automatisch eingelesen. Trenne verschiedene Bereiche sauber: eine Datei für Hosts, eine für DHCP, eine für Forwarding.
Datenbank-Administration
Datenbanken sind das Gedächtnis deiner Webanwendungen. In diesem Modul lernst du, MariaDB zu installieren, abzusichern und zu verwalten -- Benutzer anlegen, SQL-Abfragen schreiben, Backups erstellen und die Datenbank für den Einsatz mit Webanwendungen vorbereiten.
In diesem Modul arbeitest du ausschließlich auf dem Server -- alle Befehle, alle SQL-Abfragen, alle Konfigurationsdateien. Du verbindest dich zuerst per SSH (Modul 02) mit deinem Server und führst dann dort alles aus:
Hier öffnest du nur das Terminal und verbindest dich per SSH auf den Server. Sonst nichts.
Alle Befehle dieses Moduls laufen hier -- nach dem SSH-Login.
Eine Datenbank ist wie eine geordnete Werkstatt-Kartei: Jeder Eintrag hat seinen festen Platz, kann schnell gefunden und gezielt geändert werden. Der Datenbankserver (MariaDB oder MySQL) ist der Archivar -- er verwaltet alle Daten, nimmt Anfragen entgegen und liefert Ergebnisse zurück.
| Begriff | Erklärung | Werkstatt-Analogie |
|---|---|---|
| Datenbank | Sammlung zusammengehöriger Tabellen | Der ganze Aktenschrank |
| Tabelle | Strukturierte Daten mit Spalten und Zeilen | Ein Schubfach mit Karteikarten |
| Spalte (Column) | Definiert den Datentyp (Name, Datum, Betrag…) | Feld auf der Karteikarte |
| Zeile (Row) | Ein einzelner Datensatz | Eine ausgefüllte Karteikarte |
| SQL | Abfragesprache für Datenbanken | Die Sprache des Archivars |
| Primary Key | Eindeutige ID pro Zeile -- nie doppelt | Die Karteinummer |
MariaDB ist eine quelloffene Abspaltung von MySQL -- die Befehle sind nahezu identisch. Auf Debian/Ubuntu ist MariaDB der Standard. PostgreSQL ist eine weitere verbreitete Alternative, häufig für komplexere Anwendungen (Django, GIS-Daten). Für WordPress, Nextcloud und die meisten PHP-Anwendungen: MariaDB. Für Python-Projekte: oft PostgreSQL.
| Merkmal | MariaDB / MySQL | PostgreSQL |
|---|---|---|
| Verbreitung | Sehr hoch (WordPress, Nextcloud) | Hoch (Django, Ruby on Rails) |
| Einstieg | Einfach, gut dokumentiert | Etwas steiler, sehr mächtig |
| Shell-Befehl | mysql | psql |
| Backup-Tool | mysqldump | pg_dump |
| Standard-Port | 3306 | 5432 |
Zuerst installierst du MariaDB und stellst sicher, dass der Dienst läuft und beim Systemstart automatisch startet.
sudo apt update
sudo apt install mariadb-server mariadb-client
# Dienststatus prüfen -- sollte "active (running)" zeigen:
sudo systemctl status mariadb
# Autostart beim Hochfahren aktivieren:
sudo systemctl enable mariadb Nach der Installation läuft der Sicherheits-Assistent. Führe ihn immer sofort nach der Installation aus -- er setzt ein Root-Passwort, entfernt Test-Datenbanken und sperrt anonyme Zugriffe:
sudo mariadb-secure-installation
# Beantworte die Fragen so:
# Enter current password for root: [Enter drücken -- noch kein Passwort]
# Switch to unix_socket authentication? → n
# Change the root password? → y → [sicheres Passwort eingeben und merken]
# Remove anonymous users? → y
# Disallow root login remotely? → y
# Remove test database? → y
# Reload privilege tables? → y Das MariaDB-Root-Passwort ist nicht das Linux-Root-Passwort -- es ist ein separates Passwort nur für die Datenbank. Notiere es an einem sicheren Ort. Du brauchst es für Backups mit mysqldump -u root -p und für die Benutzerverwaltung.
MariaDB bietet zwei Methoden für den Root-Zugang: unix_socket (Zugriff nur als Linux-Root via sudo mysql, kein Datenbankpasswort nötig) oder ein eigenes Datenbankpasswort. Beide Methoden sind sicher. Wir wählen ein Passwort, weil es für externe Tools wie mysqldump -u root -p benötigt wird. Auf einem reinen CLI-Server ohne solche Tools wäre unix_socket die elegantere Wahl.
MariaDB hat eine eigene Kommandozeile. Du öffnest sie aus der Bash-Shell heraus und befindest dich dann im MariaDB-Prompt. Die beiden Eingabeumgebungen sehen unterschiedlich aus -- damit du immer weißt, wo du bist:
Mit diesen Befehlen öffnest du die MariaDB-Shell aus der Bash heraus:
# Als Linux-Root verbinden (unix_socket -- ohne Datenbankpasswort):
sudo mysql
# Als Root mit Datenbankpasswort verbinden:
mysql -u root -p
# Als Anwendungsbenutzer direkt in eine bestimmte Datenbank:
mysql -u werkstatt_app -p werkstatt Sobald du eingeloggt bist, siehst du: MariaDB [(none)]>. SQL-Befehle enden immer mit einem Semikolon ;. Mit EXIT; verlässt du die Shell und kommst zurück in die Bash.
-- Alle Datenbanken anzeigen:
SHOW DATABASES;
-- In eine Datenbank wechseln:
USE werkstatt;
-- Alle Tabellen der aktiven Datenbank anzeigen:
SHOW TABLES;
-- Tabellenstruktur anzeigen:
DESCRIBE kunden;
-- Aktiven Benutzer und gewählte Datenbank prüfen:
SELECT USER(), DATABASE();
-- Shell verlassen (zurück zur Bash):
EXIT; SQL-Schlüsselwörter wie SELECT, FROM oder WHERE werden traditionell in Großbuchstaben geschrieben -- das hilft, sie von Tabellen- und Spaltennamen zu unterscheiden. MariaDB ist nicht case-sensitive: select * from kunden; funktioniert genauso.
Erstelle eine Datenbank mit dem richtigen Zeichensatz (utf8mb4 unterstützt alle Zeichen inklusive Umlaute und Sonderzeichen), dann eine Tabelle darin.
-- Datenbank erstellen mit UTF-8-Zeichensatz:
CREATE DATABASE werkstatt
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- Datenbank auswählen:
USE werkstatt;
-- Tabelle erstellen:
CREATE TABLE kunden (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150),
telefon VARCHAR(30),
erstellt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tabellenstruktur prüfen:
DESCRIBE kunden;
-- Datenbank löschen (VORSICHT -- unwiderruflich!):
DROP DATABASE test_db; | Datentyp | Verwendung | Beispiel |
|---|---|---|
INT | Ganze Zahlen | id, Alter, Menge |
VARCHAR(n) | Text bis n Zeichen | Name, E-Mail, Telefon |
TEXT | Langer Text ohne Längenlimit | Beschreibungen, Notizen |
DECIMAL(m,d) | Dezimalzahlen mit d Nachkommastellen | Preise: DECIMAL(10,2) |
DATE | Datum (ohne Uhrzeit) | Geburtstag, Termin |
TIMESTAMP | Datum und Uhrzeit kombiniert | Erstell- und Änderungszeit |
BOOLEAN | Wahr/Falsch (intern TINYINT 0/1) | aktiv, bezahlt |
Die vier Grundoperationen einer Datenbank nennt man CRUD: Create (INSERT), Read (SELECT), Update (UPDATE), Delete (DELETE). Mit diesen vier Befehlen erledigst du 90 % der täglichen Datenbankarbeit.
USE werkstatt;)-- CREATE: Daten einfügen:
INSERT INTO kunden (name, email, telefon)
VALUES ('Max Mustermann', 'max@werkstatt.de', '0171-1234567');
INSERT INTO kunden (name, email, telefon)
VALUES ('Anna Schmidt', 'anna@werkstatt.de', '0172-9876543');
-- READ: Alle Datensätze abfragen:
SELECT * FROM kunden;
-- READ: Nur bestimmte Spalten und mit Filter:
SELECT name, email FROM kunden
WHERE name LIKE '%Muster%';
-- UPDATE: Einen Datensatz ändern:
UPDATE kunden
SET telefon = '0173-5555555'
WHERE id = 1;
-- DELETE: Einen Datensatz löschen:
DELETE FROM kunden
WHERE id = 2; Ein DELETE FROM kunden; ohne WHERE-Klausel löscht alle Datensätze sofort und unwiderruflich. Ein UPDATE kunden SET name='Test'; ohne WHERE überschreibt alle Namen in der Tabelle. Teste dein WHERE immer zuerst mit einem SELECT, bevor du UPDATE oder DELETE ausführst.
Du brauchst eine komplexere SQL-Abfrage? Beschreibe dein Schema und was du willst -- die KI schreibt das SQL. Beispiel: „Ich habe eine Tabelle 'kunden' mit den Spalten id, name, email, ort und umsatz (DECIMAL). Schreibe mir eine SQL-Abfrage, die alle Kunden aus Hamburg mit einem Umsatz über 5000 Euro, sortiert nach Umsatz absteigend, anzeigt."
Wie bei Linux-Benutzern gilt auch bei Datenbanken: Jede Anwendung bekommt ihren eigenen Datenbankbenutzer -- mit nur den Rechten, die sie wirklich braucht. Deine WordPress-Installation braucht niemals Root-Rechte auf der Datenbank.
-- Benutzer erstellen (nur lokaler Zugriff):
CREATE USER 'werkstatt_app'@'localhost'
IDENTIFIED BY 'S!ch3res_Passwort';
-- Alle Rechte auf eine bestimmte Datenbank vergeben:
GRANT ALL PRIVILEGES ON werkstatt.* TO 'werkstatt_app'@'localhost';
-- Nur Leserecht vergeben (z.B. für Monitoring):
GRANT SELECT ON werkstatt.* TO 'leser'@'localhost';
-- Rechte sofort anwenden:
FLUSH PRIVILEGES;
-- Rechte eines Benutzers anzeigen:
SHOW GRANTS FOR 'werkstatt_app'@'localhost';
-- Benutzer löschen:
DROP USER 'werkstatt_app'@'localhost'; | Recht | Bedeutung | Typischer Einsatz |
|---|---|---|
ALL PRIVILEGES | Alle Rechte auf die Datenbank | Anwendungs-User (WordPress, Nextcloud) |
SELECT | Nur lesen | Reporting, Monitoring |
SELECT, INSERT, UPDATE | Lesen, Einfügen, Ändern | Anwendungen ohne Löschrecht |
SELECT, INSERT, UPDATE, DELETE | Vollständiges CRUD | Standard-Webanwendung |
Remote-Zugriff absichern
Standardmäßig lauscht MariaDB nur auf 127.0.0.1 -- also nur für lokale Verbindungen auf dem Server selbst. Das ist die richtige Einstellung. Wenn du von deinem Rechner aus auf die Datenbank zugreifen musst, nutze immer einen SSH-Tunnel (Modul 02) statt den Port direkt zu öffnen.
# Prüfen, auf welcher Adresse MariaDB lauscht:
sudo ss -tulpn | grep mysql
# Ergebnis: tcp LISTEN 0 80 127.0.0.1:3306 -- das ist korrekt
# Konfigurationsdatei, falls du die Bind-Adresse prüfen willst:
grep bind-address /etc/mysql/mariadb.conf.d/50-server.cnf Öffne den MariaDB-Port 3306 niemals in der Firewall für Internetzugang. Datenbanken sind ein beliebtes Angriffsziel. Wenn du von deinem Rechner auf die Datenbank zugreifen musst, nutze einen SSH-Tunnel: ssh -L 3306:127.0.0.1:3306 admin@dein-server -- dann erreichst du die Datenbank sicher über localhost:3306 auf deinem Rechner, ohne Port 3306 zu öffnen.
Datenbankbenutzer für Remote-Zugriff immer auf eine feste IP beschränken: 'benutzer'@'10.0.0.5' statt 'benutzer'@'%' (% = alle IPs -- gefährlich!).
Datenbanken sind meistens das Wertvollste auf dem Server -- der Code lässt sich neu installieren, aber Kundendaten nicht. Lerne das Sichern und Wiederherstellen jetzt -- bevor du es brauchst.
mysqldump wird aus der normalen Bash-Shell aufgerufen -- nicht aus dem MariaDB-Prompt. Es erstellt eine SQL-Textdatei mit allen Befehlen, die die Datenbank vollständig wiederherstellen.
# Einzelne Datenbank sichern:
mysqldump -u root -p werkstatt > werkstatt_backup.sql
# Alle Datenbanken sichern:
mysqldump -u root -p --all-databases > alle_dbs_backup.sql
# Komprimiert sichern (spart Speicherplatz):
mysqldump -u root -p werkstatt | gzip > werkstatt_backup.sql.gz
# Backup wiederherstellen (Datenbank muss vorher existieren!):
mysql -u root -p werkstatt < werkstatt_backup.sql
# Komprimiertes Backup wiederherstellen:
gunzip -c werkstatt_backup.sql.gz | mysql -u root -p werkstatt Kombiniere mysqldump mit einem Cron-Job (Modul 22), damit die Datenbank täglich automatisch gesichert wird. Das Backup-Skript sollte alte Dateien nach z.B. 14 Tagen löschen, damit die Festplatte nicht volläuft. Ergänze den Dateinamen mit einem Zeitstempel: mysqldump ... > werkstatt_$(date +%Y%m%d).sql
Ausblick: Datenbankverbindung aus PHP
In Modul 17 (LAMP-Stack) lernst du, wie du aus PHP eine Verbindung zur Datenbank herstellst. Hier ein kurzer Vorgeschmack -- damit du siehst, wie Webserver, PHP und Datenbank zusammenarbeiten:
<?php
// Verbindungsdaten für die Datenbank:
$host = 'localhost';
$dbname = 'werkstatt';
$user = 'werkstatt_app';
$password = 'S!ch3res_Passwort';
// PDO-Verbindung aufbauen (empfohlene Methode):
$pdo = new PDO(
"mysql:host={$host};dbname={$dbname};charset=utf8mb4",
$user,
$password
);
// Alle Kunden abrufen:
$stmt = $pdo->query("SELECT * FROM kunden");
$kunden = $stmt->fetchAll(); Python-Anwendungen verwenden das Paket mysql-connector-python -- das Prinzip ist dasselbe: Verbindung aufbauen, SQL-Abfrage schicken, Ergebnisse verarbeiten.
Eine KI kann dir das komplette Datenbankschema für dein Projekt entwerfen. Beschreibe was du verwalten willst: „Ich baue eine Webanwendung für eine Kfz-Werkstatt. Ich brauche Tabellen für Kunden, Fahrzeuge und Reparaturaufträge. Erstelle mir die CREATE TABLE-Befehle mit sinnvollen Spalten, Datentypen und Fremdschlüssel-Beziehungen (Foreign Keys) für MariaDB."
Du installierst MariaDB auf deinem Server und führst die Grundabsicherung durch. Am Ende hast du eine laufende, gesicherte Datenbank.
sudo apt update && sudo apt install mariadb-server mariadb-clientsudo systemctl status mariadb. Du solltest "active (running)" sehen.sudo mariadb-secure-installation. Setze ein starkes Root-Passwort und beantworte alle Fragen mit der empfohlenen Antwort aus dem Modul.sudo mysql. Du siehst jetzt den MariaDB-Prompt.SHOW DATABASES; -- du solltest information_schema, mysql und performance_schema sehen, aber keine test-Datenbank mehr.EXIT;Lösungshinweise anzeigen
sudo systemctl status mariadb zeigt "Active: active (running) since...". Wenn der Dienst nicht läuft: sudo systemctl start mariadb.
Nach mariadb-secure-installation ist die test-Datenbank weg. SHOW DATABASES; listet nur noch die drei System-Datenbanken. Prüfe auch: sudo systemctl is-enabled mariadb sollte "enabled" zurückgeben.
Du erstellst eine Datenbank für eine fiktive Werkstatt, legst eine Kundentabelle an und übst alle vier CRUD-Operationen.
sudo mysqlCREATE DATABASE werkstatt CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;USE werkstatt;INSERT INTO kunden (name, email) VALUES ('Max Mustermann', 'max@test.de');SELECT * FROM kunden; und filtere dann nach einer bestimmten id: SELECT name FROM kunden WHERE id = 2;Lösungshinweise anzeigen
Die Ausgabe von SELECT * FROM kunden; zeigt alle Zeilen mit automatisch vergebenen IDs und dem automatisch gesetzten Zeitstempel in "erstellt".
Prüfe nach dem DELETE mit SELECT COUNT(*) FROM kunden; ob die Anzahl gesunken ist. Mit DESCRIBE kunden; siehst du jederzeit die Tabellenstruktur.
Du erstellst einen dedizierten Datenbankbenutzer für eine Webanwendung -- mit genau den Rechten, die nötig sind, und nicht mehr.
sudo mysqlCREATE USER 'werkstatt_app'@'localhost' IDENTIFIED BY 'TestPasswort123!';GRANT ALL PRIVILEGES ON werkstatt.* TO 'werkstatt_app'@'localhost';FLUSH PRIVILEGES; und prüfe dann: SHOW GRANTS FOR 'werkstatt_app'@'localhost';EXIT;mysql -u werkstatt_app -p werkstatt -- du solltest in die Datenbank kommen. Prüfe mit SHOW DATABASES; -- du siehst nur werkstatt, nicht die System-Datenbanken.Lösungshinweise anzeigen
SHOW GRANTS zeigt: GRANT ALL PRIVILEGES ON werkstatt.* TO 'werkstatt_app'@'localhost'. Der Login mit dem neuen Benutzer funktioniert, aber der Zugriff auf andere Datenbanken wie "mysql" wird verweigert -- genau so soll es sein.
Du sicherst die werkstatt-Datenbank mit mysqldump und stellst sie in einer neuen Testdatenbank wieder her -- so übst du den kompletten Backup-Restore-Zyklus.
mysqldump -u root -p werkstatt > /tmp/werkstatt_backup.sqlhead -30 /tmp/werkstatt_backup.sql -- du siehst SQL-Befehle (CREATE TABLE, INSERT INTO).sudo mysql, dann: CREATE DATABASE werkstatt_restore CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; und EXIT;mysql -u root -p werkstatt_restore < /tmp/werkstatt_backup.sqlUSE werkstatt_restore; SELECT * FROM kunden; -- die Daten sollten identisch mit der Original-Datenbank sein. Räume danach auf: DROP DATABASE werkstatt_restore;Lösungshinweise anzeigen
Die Backup-Datei beginnt mit Kommentarzeilen (-- MariaDB dump...) gefolgt von CREATE TABLE- und INSERT INTO-Befehlen. Die Wiederherstellung schlägt fehl, wenn die Zieldatenbank noch nicht existiert.
Nach der Wiederherstellung liefert SELECT * FROM kunden; in werkstatt_restore dieselben Zeilen wie in der Originaldatenbank. Das beweist: das Backup ist vollständig und funktioniert.
LAMP-Stack & PHP-FPM
LAMP ist wie eine vollständig eingerichtete Werkstatt: Linux ist die Halle, Apache der Empfang, MariaDB das Lager und PHP der Mechaniker. In diesem Modul baust du den Stack Schritt für Schritt auf – von der PHP-Installation bis zur ersten dynamischen Seite mit Datenbankanbindung.
In diesem Modul arbeitest du ausschließlich auf dem Server – du bist bereits per SSH eingeloggt (Modul 02). Alle Befehle, Konfigurationsdateien und PHP-Skripte liegen auf dem Server, nicht auf deinem eigenen Rechner.
Alle Befehle dieses Moduls laufen auf dem Server – nach dem SSH-Login.
LAMP steht für vier Komponenten, die zusammen eine vollständige Webserver-Umgebung bilden. Jede hat eine klare Aufgabe – wie in einer gut organisierten Werkstatt:
| Buchstabe | Komponente | Aufgabe | Paket (Debian/Ubuntu) |
|---|---|---|---|
| L | Linux | Das Betriebssystem – die Halle | – |
| A | Apache (oder Nginx) | Webserver – der Empfang, leitet Anfragen weiter | apache2 / nginx |
| M | MariaDB/MySQL | Datenbank – das Lager, speichert alles | mariadb-server |
| P | PHP | Skriptsprache – der Mechaniker, verarbeitet Logik | php-fpm |
Neben LAMP gibt es LEMP – dasselbe, aber mit Nginx statt Apache. Nginx ist schlanker und performanter bei vielen gleichzeitigen Verbindungen. In diesem Modul arbeitest du mit Nginx und PHP-FPM: das ist die empfohlene Variante für neue Projekte und lässt sich flexibel einsetzen.
PHP-FPM (FastCGI Process Manager) ist der moderne Weg, PHP zu betreiben. Statt PHP direkt in den Webserver zu laden, läuft PHP-FPM als eigener Dienst. Das ist schneller, sicherer und lässt sich besser konfigurieren.
PHP-FPM und die wichtigsten Erweiterungen installieren. Die PHP-Version kann sich unterscheiden – prüfe mit apt show php-fpm, welche Version installiert wird.
sudo apt update
sudo apt install php-fpm php-mysql php-xml php-mbstring php-curl php-zip php-gd php-intl
# PHP-Version prüfen:
php -v
# PHP-FPM-Status prüfen (Version anpassen, z.B. php8.3-fpm):
sudo systemctl status php8.3-fpm | Extension | Wozu? |
|---|---|
php-mysql | Verbindung zu MariaDB/MySQL (PDO) |
php-xml | XML verarbeiten (RSS, APIs) |
php-mbstring | Umlaute und Sonderzeichen korrekt verarbeiten |
php-curl | HTTP-Anfragen aus PHP heraus senden |
php-gd | Bilder verarbeiten und skalieren |
php-zip | ZIP-Dateien erzeugen und entpacken |
Bei Nginx übernimmt PHP-FPM die Verarbeitung von PHP-Dateien. Nginx selbst kann kein PHP ausführen – es reicht Anfragen per FastCGI an PHP-FPM weiter. Das Stichwort in der Nginx-Konfiguration ist fastcgi_pass.
Nginx installieren, falls noch nicht vorhanden (aus Modul 13 eventuell schon vorhanden), und eine Site-Konfiguration für PHP anlegen:
sudo apt install nginx
sudo systemctl enable nginx
sudo systemctl start nginx Konfigurationsdatei für die neue PHP-Site erstellen:
server {
listen 80;
server_name meine-seite.local;
root /var/www/meine-seite;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
# PHP-Anfragen an PHP-FPM weiterleiten:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
# .htaccess-Dateien nicht ausliefern:
location ~ /\.ht {
deny all;
}
} Webverzeichnis erstellen, Site aktivieren und Nginx neu laden:
sudo mkdir -p /var/www/meine-seite
sudo chown -R www-data:www-data /var/www/meine-seite
# Site aktivieren:
sudo ln -s /etc/nginx/sites-available/meine-seite.conf /etc/nginx/sites-enabled/
# Konfiguration testen:
sudo nginx -t
# Nginx neu laden:
sudo systemctl reload nginx fastcgi_pass unix:/run/php/php8.3-fpm.sock – der .sock-Pfad ist ein Unix-Socket: eine direkte interne Verbindung zwischen Nginx und PHP-FPM auf demselben Server. Das ist schneller als eine TCP-Verbindung über 127.0.0.1:9000.
PHP testen und phpinfo() sofort wieder löschen
Teste, ob PHP-FPM korrekt funktioniert. Diese Datei wird nur kurz zum Testen angelegt und danach sofort gelöscht.
echo '<?php phpinfo(); ?>' | sudo tee /var/www/meine-seite/info.php
# Im Browser aufrufen: http://SERVER-IP/info.php
# Unter "Server API" sollte "FPM/FastCGI" stehen
# SOFORT DANACH LÖSCHEN:
sudo rm /var/www/meine-seite/info.php Die phpinfo()-Seite verrät die genaue PHP-Version, alle geladenen Module, Dateipfade und Serverkonfiguration. Das ist ein Willkommensschild für Angreifer. Lass diese Datei nie dauerhaft auf dem Server liegen.
PHP-FPM verwaltet Prozesse in sogenannten Pools. Jeder Pool ist ein Satz laufender PHP-Prozesse. Die Standard-Pool-Konfiguration liegt unter /etc/php/8.3/fpm/pool.d/www.conf.
; Unter welchem Benutzer PHP-Prozesse laufen:
user = www-data
group = www-data
; Socket-Pfad (muss mit Nginx-Konfiguration übereinstimmen):
listen = /run/php/php8.3-fpm.sock
; Wie PHP-FPM Prozesse verwaltet:
pm = dynamic
; Maximale Anzahl gleichzeitiger PHP-Prozesse:
pm.max_children = 10
; Beim Start bereits gestartete Prozesse:
pm.start_servers = 2
; Mindestanzahl freier Prozesse:
pm.min_spare_servers = 1
; Maximale Anzahl freier Prozesse:
pm.max_spare_servers = 3 Nach Änderungen PHP-FPM neu starten:
sudo systemctl restart php8.3-fpm pm = dynamic bedeutet: PHP-FPM startet eine variable Anzahl Prozesse je nach Last. Für einen kleinen Server ist das ideal – es werden nicht dauerhaft viele Prozesse offen gehalten. Für stark frequentierte Server gibt es pm = static (immer dieselbe feste Anzahl).
Aus Modul 16 kennst du bereits MariaDB. Jetzt verbindest du PHP über PDO (PHP Data Objects) mit der Datenbank. PDO ist der sichere, moderne Weg – er verhindert SQL-Injection-Angriffe durch sogenannte Prepared Statements.
Schreibe nie: $sql = "SELECT * FROM users WHERE name = '$_POST[name]'". Das ist eine SQL-Injection-Lücke – ein Angreifer kann damit deine gesamte Datenbank auslesen oder löschen. Verwende immer PDO mit Prepared Statements (Platzhalter ? im SQL-Befehl).
Eine PHP-Datei mit PDO-Verbindung anlegen. Diese Datei liegt auf dem Server im Webverzeichnis.
<?php
// Datenbankverbindung per PDO herstellen
$host = 'localhost';
$db = 'meine_db';
$user = 'db_benutzer';
$pass = 'geheimes_passwort';
try {
$pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8mb4", $user, $pass);
// Fehler als Exceptions ausgeben (nur in der Entwicklung!)
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Verbindung erfolgreich!";
} catch (PDOException $e) {
// Fehlermeldung in Produktion NICHT ausgeben – ins Log schreiben!
error_log($e->getMessage());
echo "Verbindungsfehler – Details im Serverlog.";
}
?> Formular schreibt Daten sicher in die Datenbank
Ein vollständiges Beispiel: ein Formular nimmt eine Eingabe entgegen und speichert sie sicher mit einem Prepared Statement.
<?php
$pdo = new PDO('mysql:host=localhost;dbname=meine_db;charset=utf8mb4', 'db_benutzer', 'passwort');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Prepared Statement: ? ist Platzhalter, KEIN String-Einbetten
$stmt = $pdo->prepare('INSERT INTO eintraege (name, nachricht) VALUES (?, ?)');
$stmt->execute([$_POST['name'], $_POST['nachricht']]);
echo "<p>Gespeichert!</p>";
}
?>
<form method="post">
<input type="text" name="name" placeholder="Dein Name">
<textarea name="nachricht"></textarea>
<button type="submit">Speichern</button>
</form> Der ?-Platzhalter in prepare() sorgt dafür, dass Benutzereingaben niemals als SQL-Code interpretiert werden. MariaDB bekommt die Daten getrennt vom SQL-Befehl – wie ein versiegelter Briefumschlag im Vergleich zu einer offenen Postkarte.
Die php.ini steuert das Verhalten von PHP. Für PHP-FPM liegt sie unter /etc/php/8.3/fpm/php.ini. Nach jeder Änderung muss PHP-FPM neu gestartet werden.
sudo nano /etc/php/8.3/fpm/php.ini
# Nach Änderungen:
sudo systemctl restart php8.3-fpm | Einstellung | Standardwert | Empfehlung | Bedeutung |
|---|---|---|---|
upload_max_filesize | 2M | 64M | Maximale Dateigröße beim Upload |
post_max_size | 8M | 64M | Maximale POST-Datenmenge gesamt |
memory_limit | 128M | 256M | RAM-Limit pro PHP-Prozess |
max_execution_time | 30 | 60–300 | Maximale Laufzeit eines Skripts (Sekunden) |
display_errors | On | Off (Produktion) | Fehler im Browser anzeigen – nur in der Entwicklung! |
log_errors | Off | On | Fehler ins Serverlog schreiben |
error_log | – | /var/log/php_errors.log | Pfad zur Fehlerlog-Datei |
Wenn display_errors = On auf einem öffentlich erreichbaren Server steht, können Fehlermeldungen Datenbankpfade, Zugangsdaten und Codestruktur preisgeben. Auf dem Produktionsserver immer display_errors = Off und log_errors = On setzen.
Composer – Abhängigkeiten für PHP-Projekte
Composer ist das Werkzeug zum Einbinden externer PHP-Bibliotheken – vergleichbar mit apt für Pakete, aber für PHP-Code. Fast jedes moderne PHP-Projekt nutzt Composer.
# Composer herunterladen und global installieren:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
# Testen:
composer --version
# Beispiel: eine Bibliothek in ein Projekt einbinden:
cd /var/www/meine-seite
composer require monolog/monolog Für einfache PHP-Seiten brauchst du Composer noch nicht. Sobald du ein Framework oder fertige Bibliotheken (z.B. für E-Mail-Versand, PDF-Erzeugung, API-Clients) einsetzen willst, ist Composer der Standard-Weg. Er erzeugt eine composer.json-Datei mit allen Abhängigkeiten – ähnlich wie eine Teileliste in der Werkstatt.
PHP-Fehlermeldungen können kryptisch sein: Fatal error: Uncaught PDOException, Call to undefined function oder 502 Bad Gateway (Nginx kann PHP-FPM nicht erreichen). Kopiere die Fehlermeldung aus /var/log/php_errors.log oder /var/log/nginx/error.log und frag: „Ich bekomme diesen PHP-Fehler auf meinem Nginx/PHP-FPM-Server unter Ubuntu. Was bedeutet er und wie behebe ich ihn Schritt für Schritt?"
PDO-Verbindungen müssen je nach Datenbankstruktur angepasst werden. Zeig der KI deine Tabelle und frag: „Schreibe mir eine PHP-Funktion mit PDO, die sicher einen neuen Datensatz in diese Tabelle schreibt und einen bestehenden Eintrag anhand der ID aktualisiert. Benutze Prepared Statements."
Du installierst PHP-FPM und richtest Nginx so ein, dass PHP-Anfragen korrekt verarbeitet werden. Am Ende siehst du die phpinfo()-Seite im Browser – und löschst sie sofort wieder.
sudo apt install php-fpm php-mysql. Prüfe danach mit php -v die installierte Version.sudo mkdir -p /var/www/meine-seite und setze den Besitzer: sudo chown -R www-data:www-data /var/www/meine-seite/etc/nginx/sites-available/meine-seite.conf eine Nginx-Konfiguration mit dem fastcgi_pass-Block aus dem Abschnitt oben. Passe den Socket-Pfad an deine PHP-Version an.sudo ln -s /etc/nginx/sites-available/meine-seite.conf /etc/nginx/sites-enabled/. Teste mit sudo nginx -t und lade Nginx neu: sudo systemctl reload nginxecho '<?php phpinfo(); ?>' | sudo tee /var/www/meine-seite/info.php. Rufe http://SERVER-IP/info.php im Browser auf. Lösche die Datei danach sofort: sudo rm /var/www/meine-seite/info.phpLösungshinweise anzeigen
Unter „Server API" in der phpinfo()-Ausgabe muss FPM/FastCGI stehen – das bestätigt, dass Nginx die Anfragen korrekt an PHP-FPM weiterleitet. Falls du einen 502-Fehler siehst, stimmt der Socket-Pfad in der Nginx-Konfiguration nicht mit dem tatsächlichen PHP-FPM-Socket überein. Prüfe mit ls /run/php/, wie die Socket-Datei heißt.
Du verbindest PHP mit deiner MariaDB-Datenbank über PDO – sicher und ohne SQL-Injection-Risiko. Als Grundlage nutzt du eine Datenbank aus Modul 16 oder legst eine neue an.
sudo mysql. Erstelle eine neue Datenbank: CREATE DATABASE testdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'SicheresPasswort123!'; und vergib Rechte: GRANT ALL PRIVILEGES ON testdb.* TO 'testuser'@'localhost'; FLUSH PRIVILEGES; EXIT;/var/www/meine-seite/db-test.php mit der PDO-Verbindung aus dem Modul-Beispiel. Passe Datenbankname, Benutzer und Passwort an.sudo mysql testdb) führe aus: CREATE TABLE eintraege (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), nachricht TEXT, erstellt TIMESTAMP DEFAULT CURRENT_TIMESTAMP);/var/www/meine-seite/eintrag.php mit dem Formular-Beispiel. Teste das Formular im Browser und prüfe in MariaDB, ob der Eintrag gespeichert wurde: sudo mysql testdb → SELECT * FROM eintraege;Lösungshinweise anzeigen
Nach dem Absenden des Formulars solltest du „Gespeichert!" sehen. In MariaDB erscheint der neue Datensatz mit Zeitstempel. Ein häufiger Fehler: die PHP-Extension php-mysql ist nicht installiert – dann scheitert der PDO-Treiber. Prüfe mit php -m | grep pdo, ob PDO und pdo_mysql geladen sind.
Du passt wichtige PHP-Einstellungen an und verifizierst, dass die Änderungen wirksam sind. Das Upload-Limit und das Fehler-Logging werden korrekt konfiguriert.
sudo nano /etc/php/8.3/fpm/php.ini (Version ggf. anpassen). Suche mit Strg+W nach upload_max_filesize.upload_max_filesize = 32M, post_max_size = 32M, memory_limit = 256M, display_errors = Off, log_errors = On. Speichere mit Strg+O, beende mit Strg+X.sudo systemctl restart php8.3-fpmphp -i | grep upload_max_filesize und php -i | grep memory_limit. Die neuen Werte sollten erscheinen.Lösungshinweise anzeigen
php -i zeigt die aktuelle PHP-Konfiguration. Erscheinen die alten Werte, wurde entweder die falsche php.ini bearbeitet (es gibt mehrere – für CLI, FPM und Apache) oder PHP-FPM wurde nicht neu gestartet. Prüfe mit php --ini, welche Datei für die Kommandozeile gilt. Für den Webserver ist die FPM-Datei unter /etc/php/8.3/fpm/php.ini maßgeblich.
Du aktivierst den PHP-FPM Status-Endpunkt und liest Laufzeitinformationen des Pools ab – nützlich, wenn die Webseite langsam wird oder Prozesse hängen.
sudo nano /etc/php/8.3/fpm/pool.d/www.conf. Suche nach pm.status_path und entferne das Semikolon davor, sodass die Zeile lautet: pm.status_path = /status/etc/nginx/sites-available/meine-seite.conf) vor der letzten schließenden Klammer einen neuen Block ein – öffne die Datei mit sudo nano und ergänze diesen Abschnitt: den Location-Block für /status mit fastcgi_pass auf den PHP-FPM-Socket und allow 127.0.0.1; deny all; zum Schutz.sudo systemctl restart php8.3-fpm und sudo systemctl reload nginxcurl http://127.0.0.1/status. Du siehst Informationen wie aktive Prozesse, Anfragen pro Sekunde und Pool-Name.Lösungshinweise anzeigen
Die Ausgabe zeigt unter anderem active processes (aktuell arbeitende PHP-Prozesse) und total requests (Gesamtzahl aller Anfragen seit dem Start). Wenn active processes dauerhaft den Wert von max_children erreicht, ist der Pool ausgelastet – dann pm.max_children erhöhen oder Seiten-Caching einrichten.
Git – Versionskontrolle
Git ist dein digitales Reparaturprotokoll: jede Änderung wird festgehalten, du kannst jederzeit zu einem früheren Stand zurück. In diesem Modul lernst du die tägliche Arbeit mit Git – vom ersten Commit bis zum eigenen Server-Repository.
Git läuft in diesem Modul primär auf deinem eigenen Rechner. Du versionierst Dateien lokal. Optional richtest du am Ende ein Bare-Repository auf dem Server ein – das ist dein privates Remote, auf das du deine Commits hochlädst. Ab jetzt markieren wir immer, wo ein Befehl ausgeführt wird:
Hier läuft Git fast immer: git init, git add, git commit, git push – alles in deinem lokalen Terminal.
Nur für das Bare-Repository: du richtest es einmalig per SSH auf dem Server ein. Danach pushst du wieder vom eigenen Rechner.
Stell dir vor, du arbeitest an der Konfiguration eines Servers. Du änderst eine Zeile – und plötzlich startet der Dienst nicht mehr. Ohne Git musst du raten, was du verändert hast. Mit Git rewindest du einfach auf den letzten funktionierenden Stand.
Die Analogie aus der Werkstatt: Git ist wie ein lückenloses Reparaturprotokoll. Jeder Eingriff wird notiert – wer, wann, was. Du kannst jederzeit nachvollziehen, was sich geändert hat, und im Notfall zurückblättern.
Git ist das Werkzeug, das auf deinem Rechner läuft. GitHub (oder GitLab, Gitea) ist eine Plattform im Internet, die dein Git-Repository online speichert. Du kannst Git vollständig ohne GitHub nutzen – auch mit einem eigenen Server als Remote.
| Begriff | Erklärung | Analogie |
|---|---|---|
| Repository (Repo) | Projektordner mit vollständigem Änderungsverlauf | Akte mit allen Protokollblättern |
| Commit | Gespeicherter Zustand zu einem Zeitpunkt | Eintrag im Reparaturprotokoll |
| Branch | Paralleler Entwicklungszweig | Testfahrt ohne das Original anzufassen |
| Merge | Zwei Branches zusammenführen | Testergebnis ins Hauptprotokoll übernehmen |
| Remote | Kopie des Repos auf einem anderen Rechner/Server | Kopie der Akte beim Meister |
| Push | Lokale Commits zum Remote hochladen | Kopie abgeben |
| Pull | Änderungen vom Remote herunterladen | Aktualisierte Kopie abholen |
| Clone | Komplettes Repo von einem Remote herunterladen | Vollständige Akte kopieren |
Git installierst du einmalig. Danach richtest du deinen Namen und deine E-Mail-Adresse ein – diese Angaben landen in jedem Commit und sind Pflicht.
sudo apt install git
# Version prüfen:
git --version # Dein Name erscheint in jedem Commit -- einmalig setzen:
git config --global user.name "Max Mustermann"
git config --global user.email "max@example.com"
# Standard-Branch "main" statt "master":
git config --global init.defaultBranch main
# Konfiguration prüfen:
git config --list Der tägliche Git-Workflow besteht aus drei Schritten: Änderungen vorbereiten (git add), speichern (git commit) und bei Bedarf hochladen (git push). Dazwischen prüfst du immer wieder den aktuellen Stand.
Working Directory – deine Dateien, so wie sie gerade sind. Staging Area – hier sammelst du Änderungen mit git add, bevor du sie commitest. Repository – der dauerhafte Verlauf, den git commit schreibt. Wie beim Packen eines Pakets: erst einpacken (add), dann verschicken (commit).
Erstelle einen neuen Projektordner und initialisiere Git. Der Befehl git init legt einen versteckten Ordner .git/ an – dort speichert Git den gesamten Verlauf.
mkdir mein-projekt
cd mein-projekt
git init
# Erste Datei anlegen:
echo "# Mein Projekt" > README.md
# Status prüfen -- zeigt unverfolgte Dateien:
git status
# Datei zum Staging-Bereich hinzufügen:
git add README.md
# Commit mit Nachricht erstellen:
git commit -m "Projekt gestartet: README hinzugefügt"
# Verlauf anzeigen:
git log --oneline Ab jetzt läuft der Workflow immer gleich: Dateien ändern → git add . → git commit -m "Was habe ich getan?".
# Änderungen seit letztem Commit anzeigen:
git diff
# Nur gestagte Änderungen anzeigen:
git diff --staged
# Staging rückgängig machen (Datei bleibt geändert, aber nicht im nächsten Commit):
git restore --staged dateiname.txt
# Änderungen an einer Datei verwerfen (Vorsicht: unwiderruflich!):
git restore dateiname.txt Ein Branch ist ein isolierter Arbeitszweig. Du testest neue Konfigurationen oder Änderungen auf einem Branch, ohne den funktionierenden Hauptzweig (main) zu gefährden. Wenn alles klappt, führst du den Branch zusammen (Merge).
Branches anlegen, wechseln und zusammenführen:
# Alle Branches anzeigen (* = aktueller Branch):
git branch
# Neuen Branch erstellen und dorthin wechseln:
git switch -c feature/neue-config
# Zurück zu main wechseln:
git switch main
# Feature-Branch in main mergen:
git merge feature/neue-config
# Branch nach dem Merge löschen (-d prüft, ob er bereits gemerged ist):
git branch -d feature/neue-config
# Verlauf mit Branch-Struktur anzeigen:
git log --oneline --graph Ein Merge-Konflikt entsteht, wenn dieselbe Stelle in zwei Branches unterschiedlich geändert wurde. Git markiert die Konfliktstellen mit <<<<<<< und >>>>>>> in der Datei. Du öffnest die Datei, entscheidest welche Version du behältst, entfernst die Markierungen und machst dann git add . und git commit.
Nicht jede Datei soll versioniert werden. Passwörter, API-Keys, temporäre Editor-Dateien und Logs gehören nicht ins Repository. Die Datei .gitignore im Projektordner legt fest, was Git ignorieren soll. Committe sie als eine der ersten Dateien im Projekt.
Lege die .gitignore-Datei im Projektordner an. Git liest sie automatisch beim nächsten git status.
# Passwörter und Secrets -- NIEMALS ins Repo!
.env
*.key
*.pem
secrets/
# Editor-Hilfsdateien:
*.swp
*~
.vscode/
# Betriebssystem-Reste:
.DS_Store
Thumbs.db
# Log-Dateien:
*.log
/logs/
# Abhängigkeiten (werden über Paketmanager geladen):
node_modules/
vendor/ | Muster | Was wird ignoriert |
|---|---|
.env | Datei genau mit diesem Namen |
*.log | Alle Dateien mit Endung .log |
/logs/ | Ordner "logs" direkt im Projektstamm |
secrets/ | Ordner "secrets" an beliebiger Stelle im Projekt |
*.pem | Alle Zertifikatsdateien |
Wenn du versehentlich ein Passwort oder einen API-Key commitest, reicht es nicht, die Datei danach zu löschen. Der Eintrag bleibt im Verlauf erhalten und ist jederzeit einsehbar. Wechsle sofort das Passwort oder den Key. Zum Bereinigen des Verlaufs gibt es git filter-repo – das ist aber aufwendig. Nutze .gitignore konsequent von Anfang an.
Du brauchst kein GitHub für ein Remote-Repository. Du kannst auf deinem eigenen Linux-Server ein sogenanntes Bare-Repository einrichten. Das ist ein Repo ohne Arbeitskopie – nur der reine Git-Verlauf. Dein Rechner pusht dorthin, und du kannst von jedem anderen Rechner pullen oder klonen.
Erstelle auf dem Server einen Ordner für das Bare-Repository. Die Konvention ist, Bare-Repos mit .git enden zu lassen – so erkennst du sofort, dass es kein normales Arbeitsverzeichnis ist.
# Ordner anlegen (im Home-Verzeichnis des Benutzers):
mkdir -p ~/repos/mein-projekt.git
cd ~/repos/mein-projekt.git
# Bare-Repository initialisieren:
git init --bare
# Kontrollausgabe: du siehst HEAD, config, objects/, refs/ -- kein src-Ordner
ls Das Bare-Repo hat kein Working Directory – du siehst dort keine Projektdateien, nur Git-interne Strukturen. Das ist normal und gewollt. Verlasse den Server jetzt wieder:
exit Verbinde dein lokales Repository mit dem Bare-Repo auf dem Server und lade deinen ersten Stand hoch.
# Remote "origin" auf das Server-Repo zeigen lassen:
git remote add origin ssh://admin@192.168.1.50/home/admin/repos/mein-projekt.git
# Ersten Push durchführen (-u setzt den Upstream, danach reicht "git push"):
git push -u origin main
# Täglicher Push nach einem Commit:
git push
# Änderungen vom Server holen und mergen:
git pull
# Repo auf einem anderen Rechner vollständig herunterladen:
git clone ssh://admin@192.168.1.50/home/admin/repos/mein-projekt.git git push --force überschreibt den Verlauf auf dem Remote ohne Rücksicht auf vorhandene Commits. Im Alleinbetrieb selten nötig, im Team fast immer eine Katastrophe: andere verlieren ihre Arbeit. Nutze stattdessen git push --force-with-lease – das prüft zumindest, ob du den aktuellen Stand des Remotes kennst.
Diese Befehle brauchst du regelmäßig im Alltag, wenn etwas nicht wie geplant läuft:
# Verlauf kompakt mit Branch-Struktur anzeigen:
git log --oneline --graph
# Letzten Commit rückgängig machen -- Änderungen bleiben erhalten:
git reset --soft HEAD~1
# Änderungen temporär wegspeichern (z.B. um schnell den Branch zu wechseln):
git stash
git stash pop # Änderungen wieder zurückholen
# Einzelne Datei aus einem älteren Commit wiederherstellen:
# (abc1234 = Hash des Commits aus "git log --oneline")
git restore --source abc1234 server.conf
# Wer hat welche Zeile zuletzt geändert?
git blame server.conf
# Remote-Verbindungen anzeigen:
git remote -v | Befehl | Was er tut |
|---|---|
git status | Aktuellen Stand anzeigen (was ist geändert, was ist gestaged) |
git add . | Alle Änderungen stagen |
git commit -m "..." | Commit mit Nachricht erstellen |
git log --oneline | Verlauf kompakt anzeigen |
git diff | Ungestagete Änderungen anzeigen |
git switch -c name | Neuen Branch anlegen und dorthin wechseln |
git merge name | Branch in den aktuellen Branch mergen |
git stash | Änderungen zwischenparken |
git push | Commits zum Remote hochladen |
git pull | Änderungen vom Remote holen und mergen |
Die Ausgabe von git log --oneline --graph sieht für Einsteiger kryptisch aus. Kopiere sie und frag: „Erkläre mir diese git log Ausgabe auf Deutsch. Was bedeuten die Symbole, welcher Commit ist der neueste, und welche Branches gibt es?"
Merge-Konflikte mit den <<<<<<<-Markierungen sind für Einsteiger verwirrend. Kopiere den Inhalt der Konflikt-Datei und frag: „Ich habe einen Git-Merge-Konflikt in dieser Datei. Erkläre mir, welche Version ich behalten soll, und zeige mir die fertige Datei ohne Konfliktmarkierungen."
Du übst den grundlegenden Git-Workflow: Repository erstellen, Dateien tracken, Commits machen und den Verlauf lesen.
git --version. Falls es fehlt: sudo apt install git. Richte Name und E-Mail ein: git config --global user.name "Dein Name" und git config --global user.email "du@example.com".mkdir git-uebung && cd git-uebung && git init. Prüfe mit ls -la – du solltest einen versteckten Ordner .git/ sehen.echo "# Mein Übungsprojekt" > README.md. Prüfe den Status mit git status – die Datei erscheint als "untracked" (noch nicht verfolgt).git add README.md und dann git commit -m "Erster Commit: README angelegt".git diff und erstelle einen zweiten Commit mit einer aussagekräftigen Nachricht.git log --oneline. Du solltest zwei Commits sehen – der neueste steht oben.Lösungshinweise anzeigen
git log --oneline zeigt z.B.: b4f1a2c README erweitert und darunter e9d3c1a Erster Commit: README angelegt. Jeder Commit hat einen eindeutigen Hash – die kurze Buchstaben-Zahlen-Kombination. Mit diesem Hash kannst du später auf jeden Zustand zurückspringen.
Falls git status nach dem Commit "nothing to commit, working tree clean" zeigt – perfekt. Das bedeutet, alle Änderungen sind gespeichert.
Du übst Branches und schützt dein Repository davor, dass versehentlich Passwörter versioniert werden.
git switch -c feature/config. Prüfe mit git branch – der Stern (*) zeigt deinen aktuellen Branch.echo "port=8080" > server.conf. Stage und committe sie auf dem Feature-Branch: git add server.conf && git commit -m "server.conf hinzugefügt".git switch main. Prüfe mit ls – die Datei server.conf ist verschwunden! Sie existiert nur auf dem Feature-Branch. Das ist kein Fehler – genau so soll es sein.git merge feature/config. Prüfe erneut mit ls – server.conf ist jetzt in main angekommen. Lösche den Branch: git branch -d feature/config..gitignore-Datei an: echo ".env" > .gitignore. Erstelle dann eine Testdatei: touch .env. Prüfe mit git status – die .env-Datei sollte nicht als "untracked" erscheinen. Committe dann die .gitignore selbst: git add .gitignore && git commit -m ".gitignore angelegt".Lösungshinweise anzeigen
Nach Schritt 3 scheint die Datei "weg" zu sein – das ist das Kernprinzip von Branches. Git wechselt deinen Arbeitsordner auf den exakten Stand des gewählten Branches.
Nach Schritt 5 zeigt git status die .env-Datei nicht, weil sie in .gitignore steht. Die .gitignore-Datei selbst wird aber gezeigt – sie muss selbst committet werden, damit Git sie in jedem Checkout kennt.
Du richtest auf deinem Server ein eigenes Remote-Repository ein – ohne GitHub. Danach pushst du dein lokales Repository dorthin und prüfst, ob der Verlauf ankommt.
ssh admin@IP-ADRESSE. Stelle sicher, dass Git installiert ist: git --version. Falls nicht: sudo apt install git.mkdir -p ~/repos/git-uebung.git && cd ~/repos/git-uebung.git && git init --bare. Du siehst Ordner wie objects/ und refs/ – das ist ein normales Bare-Repo ohne Projektdateien. Verlasse den Server: exit.cd git-uebung. Füge das Server-Repo als Remote hinzu – ersetze IP-ADRESSE und den Benutzernamen: git remote add origin ssh://admin@IP-ADRESSE/home/admin/repos/git-uebung.git. Prüfe mit git remote -v.git push -u origin main. Das -u setzt den Upstream, danach reicht ein einfaches git push.cd ~/repos/git-uebung.git && git log --oneline aus – du siehst deinen neuen Commit.Lösungshinweise anzeigen
Das Bare-Repo auf dem Server zeigt dir mit git log den vollständigen Verlauf, obwohl keine Projektdateien sichtbar sind. Das ist das Wesen eines Bare-Repos: es ist nur für den Datenaustausch zuständig, nicht zum direkten Arbeiten.
Falls git push einen Fehler "Permission denied" gibt, prüfe, ob dein SSH-Schlüssel auf dem Server hinterlegt ist (Modul 02). Falls der Fehler "Repository not found" erscheint, stimmt der Pfad im Remote nicht – prüfe den absoluten Pfad zum Repo-Ordner auf dem Server.
Docker & Docker Compose
Docker packt eine Anwendung mit allem, was sie braucht, in einen Container – und der Container läuft auf jedem Server gleich. In diesem Modul lernst du Docker von Grund auf: einzelne Container, eigene Images und komplette Multi-Container-Stacks mit Docker Compose.
Docker wird auf dem Server installiert und dort betrieben. Du verbindest dich per SSH (Modul 02) auf den Server und gibst alle Befehle in diesem Modul dort ein. Auf deinem eigenen Rechner tust du in diesem Modul nichts – außer den Browser öffnen, um laufende Webdienste zu prüfen.
Alle Befehle in diesem Modul laufen auf dem Server – nach dem SSH-Login.
Bevor du Docker installierst, musst du verstehen, was Docker eigentlich ist – und warum es so anders ist als eine virtuelle Maschine.
Analogie Transportcontainer: Stell dir vor, du schickst Werkzeug auf eine Baustelle. Früher hast du jeden Schraubenzieher einzeln eingepackt und gehofft, dass am Zielort alles passt. Mit Docker packst du das ganze Werkzeugset – Schraubenzieher, Bohrmaschine, Steckdosenleiste – in einen genormten Transportcontainer. Der Container passt auf jedes Schiff (jeden Server) und der Inhalt funktioniert immer gleich.
| Eigenschaft | Virtuelle Maschine (VM) | Docker-Container |
|---|---|---|
| Startzeit | 1–5 Minuten | Sekunden |
| Speicherbedarf | Gigabytes (volles OS) | Megabytes (nur App + Libs) |
| Isolation | Vollständig (eigener Kernel) | Prozess-Ebene (gemeinsamer Kernel) |
| Portierbarkeit | Eingeschränkt | Läuft überall gleich |
| Typischer Einsatz | Komplette Systeme | Einzelne Dienste/Apps |
| Begriff | Erklärung | Analogie |
|---|---|---|
| Image | Unveränderlicher Bauplan für einen Container | Kochrezept |
| Container | Laufende Instanz eines Images | Das fertige Gericht |
| Dockerfile | Textdatei, die beschreibt wie ein Image gebaut wird | Dein eigenes Rezept schreiben |
| Volume | Dauerhafter Speicher außerhalb des Containers | USB-Stick, der beim Neubau erhalten bleibt |
| Registry | Online-Bibliothek für Images (z.B. Docker Hub) | App Store für Container |
| Docker Compose | Mehrere Container als Stack in einer YAML-Datei | Menüplan für ein ganzes Essen |
Docker aus dem Standard-Ubuntu-Repository ist oft veraltet. Du installierst Docker immer aus dem offiziellen Docker-Repository – das ist aktuell und offiziell unterstützt.
Installiere zuerst die nötigen Hilfspakete, dann füge das offizielle Docker-Repository hinzu:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# Verzeichnis für Schlüssel anlegen:
sudo install -m 0755 -d /etc/apt/keyrings
# Docker-GPG-Schlüssel herunterladen (für Ubuntu):
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg # Docker-Repository zur Paketliste hinzufügen:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # Deinen Benutzer zur docker-Gruppe hinzufügen:
sudo usermod -aG docker $USER
# Relogin erforderlich! Danach testen:
docker run hello-world
# Ausgabe: "Hello from Docker!" = Installation erfolgreich Der Installationsbefehl oben ist für Ubuntu. Auf Debian ersetzt du ubuntu durch debian in der Repository-URL. Der Rest ist identisch.
Das Paket docker.io aus dem Ubuntu-Standard-Repository ist oft sehr veraltet. Installiere immer aus dem offiziellen Docker-Repository wie oben gezeigt. Das Paket heißt dort docker-ce (Community Edition).
Docker-Container leben nach einem einfachen Lebenszyklus: Image herunterladen → Container starten → Container nutzen → Container stoppen → Container löschen. Das Image bleibt dabei erhalten und kann jederzeit für neue Container genutzt werden.
# Image herunterladen (ohne sofort zu starten):
docker pull nginx:latest
# Container starten:
docker run -d --name mein-nginx -p 8080:80 nginx
# -d = im Hintergrund laufen (detached)
# --name = Container einen Namen geben
# -p 8080:80 = Port weiterleiten: Server-Port 8080 → Container-Port 80
# Laufende Container anzeigen:
docker ps
# Alle Container inkl. gestoppte:
docker ps -a
# Container stoppen:
docker stop mein-nginx
# Gestoppten Container wieder starten:
docker start mein-nginx
# Container sofort stoppen UND löschen:
docker rm -f mein-nginx # Logs eines Containers anzeigen:
docker logs mein-nginx
# Logs live verfolgen (Strg+C zum Beenden):
docker logs -f mein-nginx
# Shell in einem laufenden Container öffnen:
docker exec -it mein-nginx /bin/bash
# Tipp: Wenn bash nicht verfügbar: /bin/sh verwenden
# Ressourcenverbrauch aller laufenden Container:
docker stats | Befehl | Wofür |
|---|---|
docker run -d --name NAME -p HOST:CONT IMAGE | Container starten |
docker ps | Laufende Container anzeigen |
docker ps -a | Alle Container (auch gestoppte) |
docker stop NAME | Container sauber stoppen |
docker start NAME | Gestoppten Container starten |
docker rm -f NAME | Container sofort löschen |
docker logs -f NAME | Logs live verfolgen |
docker exec -it NAME /bin/bash | Shell im Container öffnen |
Mit docker run --privileged erhält ein Container vollen Zugriff auf alle Kernel-Funktionen und Geräte des Hosts – das ist ein erhebliches Sicherheitsrisiko. Nutze --privileged nur wenn absolut notwendig und nur auf Servern, die nicht direkt aus dem Internet erreichbar sind. Ähnlich verhält es sich mit Containern, die als Root laufen: Gut gepflegte Images verwenden interne Benutzer statt Root.
# Lokale Images anzeigen:
docker images
# Ein bestimmtes Image löschen:
docker rmi nginx:latest
# Ungenutzte Images aufräumen (kein laufender Container nutzt sie):
docker image prune
# ALLES aufräumen: Images, gestoppte Container, Netzwerke:
docker system prune -a Dieser Befehl entfernt alle ungenutzten Images, gestoppten Container und Netzwerke – auch Images, die du extra heruntergeladen hast. Auf einem Produktionsserver besser nicht verwenden. Nutze stattdessen docker image prune oder lösche gezielt einzelne Images mit docker rmi IMAGE.
Container sind kurzlebig: Wenn du einen Container löschst, sind alle Daten im Container weg. Das ist wie ein Wegwerf-Feuerzeug – praktisch, aber nichts für wichtige Daten. Volumes speichern Daten außerhalb des Containers auf dem Server. Das Volume überlebt, wenn der Container gelöscht oder aktualisiert wird.
# Benanntes Volume erstellen:
docker volume create meine-daten
# Container mit Volume starten:
# -v VOLUME-NAME:PFAD-IM-CONTAINER
docker run -d --name db -v meine-daten:/var/lib/mysql mariadb:latest
# Bind-Mount: Ordner auf dem Server direkt in Container einbinden:
docker run -d --name web \
-v /home/user/html:/usr/share/nginx/html \
-p 80:80 nginx
# Alle Volumes anzeigen:
docker volume ls
# Volume-Details anzeigen (z.B. wo es auf dem Server liegt):
docker volume inspect meine-daten Benannte Volumes (-v meine-daten:/pfad) werden von Docker verwaltet und liegen unter /var/lib/docker/volumes/. Bind-Mounts (-v /dein/pfad:/pfad) verknüpfen einen beliebigen Serverordner direkt mit dem Container – praktisch für Konfigurationsdateien, die du bearbeiten willst.
Einzelne Container per docker run zu starten ist für einfache Fälle gut. Sobald mehrere Container zusammenarbeiten – z.B. eine Web-App und eine Datenbank – wird es unübersichtlich. Docker Compose beschreibt den gesamten Stack in einer einzigen docker-compose.yml-Datei. Du startest und stoppst alles mit einem Befehl.
Lege für jeden Stack ein eigenes Verzeichnis an. Die docker-compose.yml gehört immer in dieses Verzeichnis.
mkdir -p ~/stacks/wordpress
cd ~/stacks/wordpress services:
db:
image: mariadb:10.11
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: geheim123
MYSQL_DATABASE: wordpress
MYSQL_USER: wp_user
MYSQL_PASSWORD: wp_geheim
volumes:
- db_data:/var/lib/mysql
wordpress:
image: wordpress:latest
restart: unless-stopped
depends_on:
- db
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp_geheim
volumes:
- wp_data:/var/www/html
volumes:
db_data:
wp_data: # Stack starten (im Verzeichnis mit docker-compose.yml):
docker compose up -d
# Status aller Services anzeigen:
docker compose ps
# Logs aller Container des Stacks:
docker compose logs
# Logs eines bestimmten Services live verfolgen:
docker compose logs -f wordpress
# Stack stoppen (Container bleiben erhalten, Volumes auch):
docker compose down
# Images aktualisieren und neu starten:
docker compose pull
docker compose up -d Der Befehl docker compose down -v stoppt den Stack und löscht alle Volumes – damit sind Datenbankdaten und Uploads unwiederbringlich weg. Nutze immer docker compose down ohne -v, wenn du den Stack nur stoppen willst.
| Compose-Direktive | Bedeutung |
|---|---|
image: | Welches Docker-Image genutzt wird |
restart: unless-stopped | Container startet automatisch nach Reboot |
ports: | Port-Weiterleitung HOST:CONTAINER |
environment: | Umgebungsvariablen (Passwörter, Konfiguration) |
volumes: | Volume- oder Bind-Mount-Zuordnung |
depends_on: | Startreihenfolge: dieser Service wartet auf einen anderen |
networks: | In welchem internen Netzwerk der Container läuft |
Docker Compose YAML zu schreiben ist ein perfekter KI-Anwendungsfall – die Syntax ist präzise und Fehler führen zu kryptischen Meldungen. Beschreibe einfach, was du brauchst: „Erstelle mir eine docker-compose.yml für einen Stack mit Nextcloud, MariaDB und Caddy als Reverse-Proxy mit automatischem HTTPS. Nextcloud soll unter cloud.meinserver.de erreichbar sein. Erkläre jede Zeile kurz als Kommentar."
Fertige Images von Docker Hub reichen für die meisten Fälle. Wenn du aber eigene Anwendungen containerisieren willst – z.B. ein Python-Skript oder eine selbst geschriebene Web-App – baust du dein eigenes Image mit einem Dockerfile.
Erstelle ein Verzeichnis für das Projekt. Das Dockerfile beschreibt Schicht für Schicht, wie das Image aufgebaut wird:
# Basis-Image (Python 3.11, schlank):
FROM python:3.11-slim
# Arbeitsverzeichnis im Container setzen:
WORKDIR /app
# Abhängigkeitsliste zuerst kopieren (nutzt Docker-Cache):
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Anwendungscode kopieren:
COPY . .
# Welcher Port wird freigegeben (nur Dokumentation):
EXPOSE 8000
# Was läuft beim Containerstart:
CMD ["python", "app.py"] # Image bauen (. = Dockerfile im aktuellen Verzeichnis):
docker build -t meine-app:1.0 .
# Alle lokalen Images anzeigen:
docker images
# Container aus eigenem Image starten:
docker run -d --name app -p 8000:8000 meine-app:1.0
# Oder direkt in Compose nutzen:
# build: . (statt image: ...) in der docker-compose.yml | Dockerfile-Anweisung | Bedeutung |
|---|---|
FROM image:tag | Basis-Image (immer erste Zeile) |
WORKDIR /pfad | Arbeitsverzeichnis im Container setzen |
COPY quelle ziel | Datei vom Server in das Image kopieren |
RUN befehl | Befehl beim Build ausführen (z.B. apt install) |
EXPOSE port | Port dokumentieren (kein echtes Port-Mapping) |
CMD ["befehl"] | Startbefehl beim Container-Start |
ENV KEY=wert | Umgebungsvariable setzen |
Beim Build eines eigenen Images treten häufig Fehler auf, die schwer zu googeln sind – oft in der Layer-Reihenfolge oder bei apt-Paketnamen. Kopiere die Fehlermeldung und frag: „Mein Docker-Build schlägt mit folgendem Fehler fehl. Was ist die Ursache und wie passe ich das Dockerfile an? Basis-Image: python:3.11-slim, Fehler: [Fehlermeldung einfügen]"
Du installierst Docker aus dem offiziellen Repository und startest deinen ersten Container. Alle Schritte laufen auf dem Server.
docker run hello-world aus – du siehst die Ausgabe „Hello from Docker!".docker run -d --name test-web -p 8080:80 nginx. Das Image wird automatisch heruntergeladen.docker ps. Du siehst Name, Image, Port-Mapping und Laufzeit.http://SERVER-IP:8080. Du siehst die Nginx-Willkommensseite – der Container liefert sie aus.docker logs test-web. Dein Browser-Aufruf aus Schritt 4 sollte als Zugriff erscheinen.docker rm -f test-web. Prüfe mit docker ps -a, dass er weg ist.Lösungshinweise anzeigen
docker ps zeigt Spalten: CONTAINER ID, IMAGE, COMMAND, CREATED, STATUS, PORTS, NAMES. Bei Port-Mapping steht dort 0.0.0.0:8080->80/tcp – das bedeutet: Port 8080 des Servers leitet an Port 80 im Container weiter.
Nach docker rm -f test-web ist der Container weg. Das Image nginx ist aber noch lokal – prüfe das mit docker images.
Du startest einen kompletten WordPress-Stack (Web-App + Datenbank) mit einer einzigen Compose-Datei und prüfst, dass Volumes die Daten nach einem Neustart erhalten.
mkdir -p ~/stacks/wordpress && cd ~/stacks/wordpressdocker-compose.yml nach dem Beispiel im Abschnitt „Docker Compose". Öffne sie mit einem Editor: nano docker-compose.yml.docker compose up -d. Docker lädt beide Images herunter und startet die Container.docker compose ps. Beide Services (db und wordpress) müssen „running" zeigen. Falls wordpress noch startet, kurz warten.http://SERVER-IP:8080 im Browser und schließe die WordPress-Einrichtung ab. Stoppe danach den Stack: docker compose down.docker compose up -d und öffne wieder den Browser. Deine WordPress-Installation ist noch da – dank Volumes.Lösungshinweise anzeigen
docker compose ps zeigt beide Services als „running". Falls wordpress immer wieder neu startet, liegt es meist daran, dass die Datenbank noch nicht bereit ist – kurz warten, dann läuft auch WordPress.
Die Volumes db_data und wp_data bleiben bei docker compose down erhalten. Prüfe das mit docker volume ls.
Du erstellst ein einfaches eigenes Image, das eine statische HTML-Seite über Nginx ausliefert. So lernst du den Dockerfile-Workflow Schritt für Schritt.
mkdir ~/mein-image && cd ~/mein-imageecho "<h1>Hallo aus meinem Container!</h1>" > index.htmlnano Dockerfile. Inhalt: erste Zeile FROM nginx:alpine, zweite Zeile COPY index.html /usr/share/nginx/html/docker build -t meine-seite:1.0 .. Beobachte, wie Docker die Layer aufbaut.docker run -d --name test -p 9090:80 meine-seite:1.0 und öffne http://SERVER-IP:9090.Lösungshinweise anzeigen
Im Browser siehst du deine eigene HTML-Seite, ausgeliefert von Nginx im Container. docker images zeigt meine-seite:1.0 in der Liste. Das Image kannst du auf jeden anderen Server mit Docker übertragen – es läuft überall identisch.
Alpine-basierte Images sind sehr klein (wenige MB). Das ist gut für Produktions-Images – weniger Inhalt bedeutet weniger Angriffsfläche.
Passwörter direkt in der docker-compose.yml zu haben ist unpraktisch – beim Committen in Git wären sie für alle sichtbar. Du lagerst sie in eine separate .env-Datei aus.
.env-Datei: nano .env. Trage die Passwörter ein, z.B.: MYSQL_ROOT_PASSWORD=geheim123 und MYSQL_PASSWORD=wp_geheim – jede Variable in einer eigenen Zeile.docker-compose.yml an: Ersetze die festen Passwörter durch Variable-Referenzen ohne geschweifte Klammern, z.B. MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD..gitignore-Datei und trage .env ein, damit die Passwörter nicht ins Repository gelangen: echo ".env" > .gitignoredocker compose down && docker compose up -d. Docker liest die .env-Datei automatisch ein.docker compose ps. WordPress sollte wieder unter http://SERVER-IP:8080 erreichbar sein.Lösungshinweise anzeigen
Docker Compose liest .env im gleichen Verzeichnis automatisch ein – du musst nichts extra angeben. Die Variablen werden in der YAML-Datei mit $VARIABLENNAME referenziert.
Die .env-Datei enthält die echten Geheimnisse und darf nie in Git landen. Die docker-compose.yml hingegen kann problemlos committet werden – sie enthält nur noch Variablennamen, keine Passwörter.
Virtualisierung
Eine virtuelle Maschine ist wie ein zweiter Arbeitsplatz im Computer -- vollständig abgetrennt, mit eigenem Betriebssystem. In diesem Modul lernst du, wie du mit KVM/QEMU auf deinem Server echte VMs betreibst, sie verwaltest und mit Snapshots absicherst.
In diesem Modul arbeitest du ausschließlich auf dem Server. KVM und QEMU laufen dort -- du richtest sie ein, startest VMs und verwaltest sie. Von deinem eigenen Rechner aus verbindest du dich nur per SSH auf den Server, um Befehle einzugeben.
Alle Befehle in diesem Modul laufen auf dem Server. Du bist per SSH eingeloggt -- genau wie in Modul 02 gelernt.
Bevor wir loslegen, klären wir kurz, welche Option wann sinnvoll ist. Stell dir einen Parkplatz vor: Bare Metal ist ein einzelnes Auto direkt auf dem Platz. Eine VM ist ein Parkhaus -- mehrere Fahrzeuge übereinander, jedes vollständig abgetrennt. Ein Container ist ein Kleinbus mit mehreren Personen -- teilt sich das Fahrzeug, aber jeder hat seinen eigenen Sitz.
| Eigenschaft | Bare Metal | Virtuelle Maschine (VM) | Container (Docker) |
|---|---|---|---|
| Betriebssystem | Direkt auf Hardware | Eigener Kernel, beliebig | Teilt Host-Kernel |
| Startzeit | Minuten (Reboot) | 1--3 Minuten | Sekunden |
| Isolation | Keine (alles direkt) | Hardware-Level | Prozess-Level |
| Ressourcenverbrauch | Minimal | Hoch (eigener Kernel) | Gering |
| Andere OS möglich? | Nein | Ja (Windows, BSD, ...) | Nein (nur Linux) |
| Typischer Einsatz | Einzelne dedizierte Dienste | Komplette Server, Tests, Windows | Anwendungen, Microservices |
Du brauchst eine VM, wenn: du ein anderes Betriebssystem brauchst (z.B. Windows), du komplette Isolation bis auf Hardware-Ebene willst, oder du eine ganze Server-Umgebung nachbauen willst. Für Webanwendungen, Datenbanken und einzelne Dienste reicht Docker (aus Modul 19) fast immer.
KVM (Kernel-based Virtual Machine) ist direkt im Linux-Kernel eingebaut. QEMU übernimmt die Emulation der Hardware. Zusammen bilden sie die Basis für professionelle Virtualisierung -- auch Proxmox und viele Cloud-Anbieter bauen darauf auf.
Damit KVM funktioniert, muss deine CPU Hardware-Virtualisierung unterstützen. Das ist bei allen modernen Prozessoren der Fall, muss aber manchmal im BIOS aktiviert werden.
Prüfe zuerst, ob die CPU deines Servers Hardware-Virtualisierung unterstützt. Das Tool kvm-ok ist am einfachsten:
# Schnellprüfung mit kvm-ok (aus dem Paket cpu-checker):
sudo apt install cpu-checker
kvm-ok
# Erwartete Ausgabe: "KVM acceleration can be used"
# Alternativ: CPU-Flags direkt prüfen
# vmx = Intel VT-x, svm = AMD-V
grep -cE 'vmx|svm' /proc/cpuinfo
# Ergebnis muss größer 0 sein Wenn kvm-ok meldet "KVM acceleration can NOT be used", aktiviere Virtualisierung im BIOS (Intel VT-x oder AMD-V).
Jetzt installierst du alle nötigen Pakete. libvirt ist die Verwaltungsschicht, über die du VMs mit virsh steuern kannst:
sudo apt update
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst
# Deinen Benutzer zu den nötigen Gruppen hinzufügen:
sudo usermod -aG libvirt $USER
sudo usermod -aG kvm $USER
# Abmelden und neu einloggen, damit die Gruppenänderungen greifen!
# Danach: Dienst prüfen
sudo systemctl status libvirtd virt-install ist ein Kommandozeilen-Werkzeug, mit dem du VMs direkt auf dem Server erstellen kannst. Du gibst an, wie viel RAM, wie viele CPU-Kerne und wie viel Speicher die VM bekommt -- und von welchem ISO sie starten soll.
Ein häufiger Anfängerfehler: VMs bekommen zusammen mehr RAM zugewiesen als der Server physisch hat. Linux erlaubt zwar RAM-Overcommitment, aber wenn alle VMs gleichzeitig viel Arbeitsspeicher brauchen, bricht das System zusammen. Faustregel: Weise nie mehr als 80 % des physischen RAMs an VMs zu. Behalte immer genug für das Host-System (mindestens 1--2 GB).
Lade zuerst ein ISO-Image herunter, dann erstelle die VM. Das ISO wird im Standard-Speicherverzeichnis von libvirt abgelegt:
# ISO in den libvirt-Bildordner herunterladen (Debian Netinstall, ~400 MB):
sudo wget -O /var/lib/libvirt/images/debian-12.iso \
https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.10.0-amd64-netinst.iso
# VM erstellen (Debian mit 2 GB RAM, 2 CPUs, 20 GB Disk):
sudo virt-install \
--name debian-test \
--ram 2048 \
--vcpus 2 \
--disk path=/var/lib/libvirt/images/debian-test.qcow2,size=20 \
--os-variant debian12 \
--cdrom /var/lib/libvirt/images/debian-12.iso \
--network network=default \
--graphics vnc,listen=0.0.0.0 \
--noautoconsole
# --noautoconsole: VM startet im Hintergrund
# Verbindung über VNC-Client: Server-IP:5900 Die VM läuft jetzt im Hintergrund. Du verbindest dich zur grafischen Konsole über einen VNC-Client (z.B. Remmina oder TigerVNC) auf Port 5900 deines Servers, um die Installation abzuschließen.
virsh ist dein zentrales Werkzeug für alles rund um VMs. Denk daran wie an einen Schalttafel-Meister: Er startet Maschinen, fährt sie runter, zeigt Status und erstellt Sicherungen -- alles über kurze Befehle.
# Alle VMs anzeigen (laufende und gestoppte):
sudo virsh list --all
# VM starten:
sudo virsh start debian-test
# VM sauber herunterfahren (wie "shutdown -h now" von innen):
sudo virsh shutdown debian-test
# VM sofort stoppen (wie Stecker ziehen -- Datenverlust möglich!):
sudo virsh destroy debian-test
# VM neu starten:
sudo virsh reboot debian-test
# Detaillierte Infos zur VM:
sudo virsh dominfo debian-test
# VM-Konfiguration anzeigen (XML):
sudo virsh dumpxml debian-test
# VM löschen (mit allen Disk-Images!):
sudo virsh undefine debian-test --remove-all-storage virsh destroy stoppt die VM sofort (wie Stecker ziehen), löscht aber keine Dateien. virsh undefine --remove-all-storage hingegen löscht die VM und ihr gesamtes Disk-Image unwiderruflich -- ohne Rückfrage. Vor dem Löschen immer prüfen, ob ein Backup oder Snapshot existiert.
Die wichtigsten virsh-Befehle auf einen Blick:
| Befehl | Wirkung | Analogie |
|---|---|---|
virsh list --all | Alle VMs anzeigen | Fuhrparkübersicht |
virsh start | VM starten | Zündung an |
virsh shutdown | VM sauber herunterfahren | Motor sauber abstellen |
virsh destroy | VM sofort stoppen | Notaus-Schalter |
virsh snapshot-create-as | Snapshot erstellen | Sicherungspunkt setzen |
virsh snapshot-revert | Snapshot wiederherstellen | Auf Sicherungspunkt zurück |
virsh undefine | VM löschen | Fahrzeug aus Fuhrpark aussondern |
Ein Snapshot speichert den exakten Zustand einer VM zu einem bestimmten Zeitpunkt: Dateisystem und optional den Arbeitsspeicher. Wenn danach etwas schiefläuft -- ein Update die VM zerstört, eine Konfiguration nicht funktioniert -- rollst du einfach zum Snapshot zurück. Kein Datenverlust, keine Neuinstallation.
# Snapshot erstellen (VM sollte gestoppt oder pausiert sein für konsistente Daten):
sudo virsh snapshot-create-as debian-test \
--name "vor-update" \
--description "Zustand vor apt upgrade"
# Alle Snapshots einer VM anzeigen:
sudo virsh snapshot-list debian-test
# Details eines Snapshots anzeigen:
sudo virsh snapshot-info debian-test vor-update
# Zum Snapshot zurückkehren (VM wird gestoppt und neu gestartet):
sudo virsh snapshot-revert debian-test vor-update
# Snapshot löschen (wenn er nicht mehr gebraucht wird):
sudo virsh snapshot-delete debian-test vor-update Mach dir zur Gewohnheit: Vor jedem größeren Eingriff (System-Update, neue Software, Konfigurationsänderung) einen Snapshot. Der Name sollte selbsterklärend sein: "vor-nginx-update" statt "snapshot1". Alte Snapshots kosten Speicherplatz -- lösche sie, wenn die Änderung stabil läuft.
Jede VM braucht Netzwerkzugang. libvirt bietet zwei Hauptmodi:
- NAT (Network Address Translation): Die VM bekommt eine private IP (z.B. 192.168.122.x) und kommt über den Host ins Internet. Von außen ist die VM nicht direkt erreichbar. Gut für: Test-VMs, Entwicklungsumgebungen.
- Bridge: Die VM hängt direkt im Heimnetzwerk, wie ein eigener Rechner. Sie bekommt eine IP vom Router (z.B. 192.168.1.x) und ist von überall im Netz erreichbar. Gut für: Server-VMs, die Dienste nach außen anbieten.
Das Standard-Netzwerk von libvirt nutzt NAT über die virtuelle Bridge virbr0. So prüfst und verwaltest du es:
# Alle libvirt-Netzwerke anzeigen:
sudo virsh net-list --all
# Details des Standard-Netzwerks (NAT, 192.168.122.0/24):
sudo virsh net-info default
# Standard-Netzwerk starten (falls inaktiv):
sudo virsh net-start default
# Standard-Netzwerk beim Systemstart automatisch starten:
sudo virsh net-autostart default
# Welche IP-Adressen hat libvirt vergeben? (DHCP-Leases):
sudo virsh net-dhcp-leases default Für eine Bridge-Konfiguration (VM direkt im Heimnetz) erstellst du auf dem Host eine Linux-Bridge und verbindest deine Netzwerkkarte damit. Das konfigurierst du in /etc/netplan/ (Ubuntu) oder /etc/network/interfaces (Debian) -- wie in Modul 05 gelernt.
Wenn du eine VM vor allem brauchst, weil du ein vollständiges Ubuntu oder Debian isoliert betreiben willst -- aber kein Windows oder andere Betriebssysteme -- dann schau dir LXC/LXD an. LXC-Container sind wie VMs von außen betrachtet, teilen aber den Kernel mit dem Host. Sie starten in Sekunden und brauchen deutlich weniger Ressourcen.
# LXD installieren (via Snap -- empfohlene Methode auf Ubuntu):
sudo snap install lxd
# LXD initialisieren (setzt Netzwerk und Speicher auf Standardwerte):
lxd init --minimal
# Deinen Benutzer zur lxd-Gruppe hinzufügen:
sudo usermod -aG lxd $USER
# Danach: Abmelden und neu einloggen!
# Ersten Container starten (Ubuntu 22.04):
lxc launch ubuntu:22.04 mein-container
# In den Container einsteigen (wie SSH, aber lokal):
lxc exec mein-container -- bash
# Container stoppen und löschen:
lxc stop mein-container
lxc delete mein-container
# Alle Container anzeigen:
lxc list Docker (Modul 19): Einzelne Anwendungen und Microservices, schnell deployen und portabel.
LXC/LXD: Vollständige Linux-Umgebung ohne den Overhead einer VM -- z.B. ein komplettes Ubuntu mit eigenem Netzwerk-Stack, aber kein Windows möglich.
KVM/QEMU VM: Wenn du ein anderes Betriebssystem brauchst (z.B. Windows), oder wenn maximale Isolation gefordert ist (Sicherheits-Tests, Kunden-Isolation).
virt-install hat Dutzende Parameter. Wenn du eine spezielle Konfiguration brauchst -- z.B. mehrere Netzwerkkarten, UEFI statt BIOS, oder verschachtelte Virtualisierung (nested KVM) -- beschreibe dein Ziel: „Ich möchte mit virt-install eine VM erstellen, die zwei Netzwerkkarten hat: eine im NAT-Netzwerk und eine direkt auf meiner Bridge br0. Zeige mir den vollständigen virt-install-Befehl für Debian 12."
Du prüfst, ob dein Server Virtualisierung unterstützt, und installierst alle nötigen Pakete. Das ist die Grundlage für alle weiteren VM-Aufgaben.
sudo apt install cpu-checker && kvm-ok. Notiere, was die Ausgabe meldet.sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients virtinst.sudo systemctl status libvirtd. Er sollte als "active (running)" angezeigt werden.sudo usermod -aG libvirt $USER. Logge dich per exit aus und verbinde dich per SSH neu, damit die Gruppenänderung wirkt.sudo virsh net-list --all. Das Netzwerk "default" sollte dort erscheinen. Starte es bei Bedarf: sudo virsh net-start default.Lösungshinweise anzeigen
kvm-ok meldet entweder "KVM acceleration can be used" (gut) oder "can NOT be used" (BIOS-Einstellung prüfen: Intel VT-x bzw. AMD-V aktivieren). virsh net-list --all zeigt "default" mit Status "active" oder "inactive". Das Standard-Netzwerk nutzt NAT mit dem Adressbereich 192.168.122.0/24 über die Bridge virbr0.
Du lädst ein Debian-ISO herunter, erstellst eine virtuelle Maschine mit virt-install und sicherst den Zustand mit einem Snapshot. Du brauchst mindestens 5 GB freien Speicher auf dem Server.
df -h /var/lib/libvirt/images/. Du brauchst mindestens 5 GB.sudo wget -O /var/lib/libvirt/images/debian-12.iso https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.10.0-amd64-netinst.iso. Das dauert je nach Leitung einige Minuten (ca. 400 MB).sudo virt-install --name debian-test --ram 2048 --vcpus 2 --disk path=/var/lib/libvirt/images/debian-test.qcow2,size=5 --os-variant debian12 --cdrom /var/lib/libvirt/images/debian-12.iso --network network=default --graphics vnc,listen=0.0.0.0 --noautoconsole.sudo virsh list --all. Notiere den Status.sudo virsh destroy debian-test (sofortiger Stopp, da noch kein OS installiert). Erstelle dann einen Snapshot: sudo virsh snapshot-create-as debian-test --name "nach-erstellung" --description "Leere VM nach virt-install".sudo virsh snapshot-list debian-test. Lösche danach die Test-VM: sudo virsh undefine debian-test --remove-all-storage.Lösungshinweise anzeigen
virsh list --all zeigt VMs mit Status "running", "shut off" oder "idle". Der Snapshot-Name "nach-erstellung" erscheint in snapshot-list mit Erstellungszeit. undefine --remove-all-storage löscht die VM-Definition und das .qcow2-Disk-Image -- danach ist beides weg. Das ISO in /var/lib/libvirt/images/ bleibt erhalten.
Du startest einen LXC-Container mit Ubuntu, erkundest ihn von innen und vergleichst, was sich vom Host-System unterscheidet.
sudo snap install lxd. Füge deinen Benutzer zur lxd-Gruppe hinzu: sudo usermod -aG lxd $USER. Abmelden und neu einloggen per SSH.lxd init --minimal.lxc launch ubuntu:22.04 test-lxc. Beobachte, wie schnell er bereit ist -- Sekunden statt Minuten.lxc exec test-lxc -- bash. Prüfe darin: uname -a (Kernel -- derselbe wie der Host!), hostname (Container-Name), ip addr (eigene IP). Tippe exit um zurückzukehren.lxc info test-lxc. Stoppe und lösche ihn: lxc stop test-lxc && lxc delete test-lxc. Prüfe mit lxc list, dass er weg ist.Lösungshinweise anzeigen
Im Container zeigt uname -a denselben Kernel wie der Host -- das ist der Kernunterschied zu einer KVM-VM, die einen eigenen Kernel lädt. lxc info test-lxc zeigt CPU- und Speicherverbrauch: ein LXC-Container im Leerlauf belegt oft unter 50 MB RAM, während eine KVM-VM mit eigenem Kernel 200--500 MB benötigt. lxc list nach dem Löschen zeigt eine leere Liste.
Nicht sicher, welche Virtualisierungsform für dein Vorhaben passt? Beschreibe es konkret: „Ich möchte auf meinem Heimserver drei Dienste betreiben: eine Windows-Anwendung für Buchhaltung, einen Nextcloud-Server und einen Pi-hole-DNS. Soll ich KVM-VMs, LXC-Container, Docker-Container oder eine Kombination nutzen? Erkläre Vor- und Nachteile für jede Option und nenne mir die nötigen Ressourcen."
Bash-Scripting
Wiederkehrende Aufgaben von Hand ausführen? Das überlässt du ab jetzt einem Skript. Ein Bash-Skript ist wie eine Checkliste – du schreibst einmal auf, was zu tun ist, und der Server arbeitet sie selbstständig ab.
Bash-Skripte schreibst und führst du auf dem Server aus – du bist also bereits per SSH eingeloggt, bevor du in diesem Modul irgendeinen Befehl tippst. In diesem Modul gilt durchgängig der Server-Kontext:
Alle Skripte und Befehle dieses Moduls laufen auf dem Server – nach dem SSH-Login aus Modul 02.
Verbinde dich zuerst per SSH auf deinen Server (Modul 02). Alle Skripte in diesem Modul erstellst und führst du dort aus. Du brauchst kein separates lokales Terminal.
Ein Bash-Skript ist eine ganz normale Textdatei. Du schreibst Befehle hinein – einen pro Zeile – und der Server führt sie der Reihe nach aus. Wie eine Checkliste in der Werkstatt: Schritt 1, Schritt 2, Schritt 3 – fertig.
Erstelle eine neue Datei mit nano, mach sie ausführbar und starte sie:
# 1. Skript erstellen (öffnet den nano-Editor):
nano hallo.sh
# 2. Ausführbar machen (ohne diesen Schritt passiert nichts):
chmod +x hallo.sh
# 3. Skript starten:
./hallo.sh Das ./ vor dem Dateinamen sagt Bash: „Suche die Datei im aktuellen Verzeichnis, nicht irgendwo im Systempfad."
#!/bin/bash
# Mein erstes Bash-Skript
echo "Hallo! Ich bin ein Bash-Skript."
echo "Heute ist: $(date +%d.%m.%Y)"
echo "Du bist eingeloggt als: $(whoami)"
echo "Dein Server heißt: $(hostname)" Die erste Zeile #!/bin/bash heißt „Shebang". Sie sagt dem System, welches Programm das Skript ausführen soll. Ohne diese Zeile weiß Linux nicht, dass es ein Bash-Skript ist. Der Shebang muss immer in der allerersten Zeile stehen – keine Leerzeile davor!
Variablen speichern Werte, die du im Skript mehrfach verwenden kannst. Stell dir eine Variable wie ein Etikett auf einem Behälter vor: du schreibst etwas drauf (Zuweisung) und liest es später ab (Auslesen).
#!/bin/bash
# Variablen setzen – KEIN Leerzeichen um das = !
NAME="Max"
ALTER=30
SERVER="webserver-01"
# Variablen auslesen – mit $ davor:
echo "Hallo $NAME, du bist $ALTER Jahre alt."
echo "Server: ${SERVER}"
# Befehlsausgabe in einer Variable speichern:
DATUM=$(date +%Y-%m-%d)
HOSTNAME_SERVER=$(hostname)
echo "Datum: $DATUM, Server: $HOSTNAME_SERVER"
# Benutzereingabe einlesen:
read -p "Wie heißt du? " EINGABE
echo "Willkommen, $EINGABE!" NAME="Max" ist korrekt. NAME = "Max" ist ein Fehler! Bash versucht dann den Befehl NAME mit dem Argument = auszuführen – was nicht existiert. Das ist einer der häufigsten Anfängerfehler.
Skripte können auch Argumente von der Kommandozeile entgegennehmen – ähnlich wie Befehle Optionen akzeptieren. Die speziellen Variablen $1, $2 usw. enthalten diese Argumente:
#!/bin/bash
# Aufruf: ./argumente.sh Max 30
echo "Skriptname: $0"
echo "Erstes Argument: $1"
echo "Zweites Argument: $2"
echo "Alle Argumente: $@"
echo "Anzahl Argumente: $#" | Variable | Bedeutung | Beispiel |
|---|---|---|
$0 | Name des Skripts selbst | ./backup.sh |
$1, $2 … | Argumente (1., 2. Argument …) | $1 = erster Wert nach dem Skriptnamen |
$@ | Alle Argumente als Liste | nützlich in Schleifen |
$# | Anzahl der übergebenen Argumente | 0 wenn keins übergeben |
$? | Rückgabecode des letzten Befehls | 0 = Erfolg, alles andere = Fehler |
$$ | Prozess-ID des laufenden Skripts | nützlich für temporäre Dateinamen |
Mit Bedingungen reagiert dein Skript auf verschiedene Situationen – wie eine Weiche an einer Schiene: Je nach Zustand wird ein anderer Weg eingeschlagen.
#!/bin/bash
# Prüfen ob eine Datei existiert:
if [ -f "/etc/nginx/nginx.conf" ]; then
echo "Nginx ist installiert."
else
echo "Nginx ist NICHT installiert."
fi
# Prüfen ob ein Verzeichnis existiert:
if [ -d "/var/www/html" ]; then
echo "Webverzeichnis vorhanden."
fi
# Zahlenvergleich mit elif:
AUSLASTUNG=$(df / --output=pcent | tail -1 | tr -d ' %')
if [ $AUSLASTUNG -gt 80 ]; then
echo "WARNUNG: Festplatte zu ${AUSLASTUNG}% belegt!"
elif [ $AUSLASTUNG -gt 60 ]; then
echo "Hinweis: Festplatte zu ${AUSLASTUNG}% belegt."
else
echo "Festplatte OK: ${AUSLASTUNG}% belegt."
fi
# String-Vergleich:
if [ "$USER" = "root" ]; then
echo "Du bist Root – Vorsicht!"
fi | Test | Bedeutung |
|---|---|
-f datei | Datei existiert (und ist eine reguläre Datei) |
-d pfad | Verzeichnis existiert |
-e pfad | Datei oder Verzeichnis existiert (beides) |
-z "$var" | Variable ist leer (Zero length) |
-n "$var" | Variable ist nicht leer (Non-zero) |
-eq / -ne | Gleich / Ungleich (Zahlen) |
-gt / -lt | Größer als / Kleiner als (Zahlen) |
-ge / -le | Größer-gleich / Kleiner-gleich (Zahlen) |
= / != | Gleich / Ungleich (Zeichenketten) |
Schleifen führen einen Codeblock mehrfach aus – wie ein Monteur, der dieselbe Inspektion an jedem Fahrzeug in der Reihe macht, ohne den Ablauf jedes Mal neu zu beschreiben.
for-Schleife
#!/bin/bash
# Über eine feste Liste iterieren:
for DIENST in ssh cron nginx; do
echo "Prüfe Dienst: $DIENST"
systemctl is-active --quiet $DIENST \
&& echo " OK: laeuft" \
|| echo " GESTOPPT"
done
# Über Dateien iterieren:
for DATEI in /var/log/*.log; do
GROESSE=$(du -h "$DATEI" | awk '{print $1}')
echo "$DATEI: $GROESSE"
done
# Zahlenbereich mit seq:
for i in $(seq 1 5); do
echo "Schritt $i von 5"
done while-Schleife
#!/bin/bash
# Datei Zeile für Zeile einlesen:
while IFS= read -r ZEILE; do
echo "Verarbeite: $ZEILE"
done < /etc/hosts
# Dauerhaft auf ein Ereignis warten (Monitoring-Schleife):
while true; do
if ! systemctl is-active --quiet nginx; then
echo "$(date): Nginx gestoppt – starte neu"
sudo systemctl start nginx
fi
sleep 60
done Funktionen fassen Codeblöcke zusammen, die du mehrfach aufrufen willst – wie eine Subroutine im Werkstatt-Handbuch: einmal definiert, beliebig oft einsetzbar.
#!/bin/bash
# Funktion definieren – geschweifte Klammern enthalten den Rumpf:
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Funktion die prüft ob ein Dienst läuft:
dienst_laeuft() {
systemctl is-active --quiet "$1"
}
# Funktionen aufrufen:
log "Skript gestartet"
if dienst_laeuft "nginx"; then
log "Nginx laeuft."
else
log "Nginx ist gestoppt!"
fi
log "Skript beendet" Definiere Funktionen immer vor dem ersten Aufruf – am besten am Anfang des Skripts, direkt nach den Variablen. Bash liest Skripte von oben nach unten. Eine Funktion, die erst nach dem Aufruf steht, ist noch nicht bekannt.
Ein Skript, das bei einem Fehler einfach weiterläuft, kann gefährlich sein – wie ein Mechaniker, der trotz Fehler-Anzeige weitermacht. Mit ein paar Sicherheitsoptionen machst du dein Skript von Anfang an widerstandsfähig.
#!/bin/bash
# Sicherheitsoptionen – direkt nach dem Shebang:
set -euo pipefail
# -e: Bei jedem Fehler sofort abbrechen
# -u: Abbrechen wenn eine undefinierte Variable benutzt wird
# -o pipefail: Fehler in Pipes nicht ignorieren
QUELLE="/var/www/html"
# Prüfung mit gezieltem exit (besser als $? bei set -e):
if [ ! -d "$QUELLE" ]; then
echo "FEHLER: $QUELLE existiert nicht!"
exit 1
fi
# $? direkt nach einem Befehl auslesen (ohne set -e):
ls "$QUELLE" &>/dev/null
if [ $? -eq 0 ]; then
echo "Verzeichnis lesbar – OK"
fi
echo "Alles OK."
exit 0 Mit set -e bricht Bash bei jedem Fehler sofort ab. Das bedeutet: Eine Prüfung wie if [ $? -ne 0 ] nach einem fehlgeschlagenen Befehl wird nie erreicht – das Skript ist vorher schon abgebrochen. Verwende daher bei aktivem set -e immer direkte Prüfungen mit if befehl; then oder if [ -f ... ] statt nachträglichem $?.
Skripte mit rm -rf können Daten unwiederbringlich löschen, wenn eine Variable leer oder falsch belegt ist. Prüfe immer zuerst mit if [ -n "$VARIABLE" ], dass die Variable nicht leer ist. Noch besser: Teste das Skript zuerst mit echo statt dem echten Befehl.
rm -rf $VERZEICHNIS/ kann bei einer leeren Variable das gesamte Wurzelverzeichnis löschen. Mit set -u fängt Bash eine undefinierte Variable ab und bricht ab. Ohne diesen Schutz passiert die Katastrophe still. Nutze immer set -euo pipefail und explizite Pfadprüfungen.
Dieses vollständige Server-Health-Check-Skript kombiniert alles aus diesem Modul:
#!/bin/bash
set -euo pipefail
LOGFILE="/var/log/server-check.log"
WARN_DISK=80
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
}
check_disk() {
USAGE=$(df / --output=pcent | tail -1 | tr -d ' %')
if [ $USAGE -gt $WARN_DISK ]; then
log "WARNUNG: Festplatte bei ${USAGE}%"
else
log "OK: Festplatte bei ${USAGE}%"
fi
}
check_service() {
if systemctl is-active --quiet "$1"; then
log "OK: $1 laeuft"
else
log "FEHLER: $1 ist gestoppt!"
fi
}
log "=== Server Health-Check ==="
check_disk
check_service "nginx"
check_service "ssh"
log "=== Check abgeschlossen ===" Bash-Scripting ist ein idealer KI-Anwendungsfall: Beschreibe, was dein Skript tun soll, und lass es erstellen. Beispiel-Prompt: „Schreibe mir ein Bash-Skript, das jeden Tag um 3 Uhr nachts alle MariaDB-Datenbanken sichert, die Backups mit Zeitstempel benennt und Backups älter als 7 Tage löscht. Mit set -euo pipefail, einer log()-Funktion und Fehlermeldung falls mysqldump nicht gefunden wird."
Bash-Fehlermeldungen sind manchmal kryptisch. Wenn dein Skript abbricht, kopiere die Fehlermeldung und frag: „Mein Bash-Skript bricht mit dieser Fehlermeldung ab: [Fehlermeldung einfügen]. Das Skript macht folgendes: [Kurzbeschreibung]. Was ist der Fehler und wie behebe ich ihn?"
Du schreibst dein erstes eigenes Skript auf dem Server und lernst den kompletten Workflow: Erstellen, ausführbar machen, starten.
nano sysinfo.sh. Tippe den Shebang als erste Zeile: #!/bin/bashuname -r), Uptime und Festplattenbelegung (df -h /) ausgeben. Nutze echo und Befehlssubstitution mit $(befehl).chmod +x sysinfo.sh./sysinfo.sh – du solltest alle Systeminformationen sehen.tee -a sysinfo.log. Prüfe das Ergebnis mit cat sysinfo.log.Lösungshinweise anzeigen
Das Skript gibt alle Systeminformationen auf dem Bildschirm aus. Mit ./sysinfo.sh | tee sysinfo.log wird die Ausgabe gleichzeitig in eine Datei geschrieben. Alternativ direkt im Skript: echo "$(hostname)" | tee -a sysinfo.log. Schau dir nach dem ersten Lauf die Logdatei an – alle Zeilen sollten dort stehen.
Du schreibst ein Skript, das mehrere Dienste in einer Schleife prüft und am Ende eine Zusammenfassung ausgibt.
nano dienst-check.sh. Beginne mit #!/bin/bash. Lasse set -e hier weg – die Schleife soll auch bei gestoppten Diensten weiterlaufen.DIENSTE="ssh cron" (diese laufen auf jedem Ubuntu-Server – füge weitere hinzu wie nginx oder mariadb wenn installiert).systemctl is-active --quiet prüft und per if/else ausgibt, ob er läuft. Initialisiere einen Zähler vor der Schleife: RUNNING=0. Erhöhe ihn bei laufenden Diensten: RUNNING=$((RUNNING + 1)).TOTAL=$(echo $DIENSTE | wc -w). Gib am Ende aus: echo "$RUNNING von $TOTAL Diensten laufen."chmod +x dienst-check.sh && ./dienst-check.sh. Alle bekannten Dienste sollten als laufend gemeldet werden.Lösungshinweise anzeigen
systemctl is-active --quiet DIENST gibt Exit-Code 0 zurück wenn der Dienst läuft, sonst einen anderen Wert. if nutzt diesen Exit-Code direkt aus – ohne $? prüfen zu müssen. Der Zähler läuft nur hoch, wenn ein Dienst aktiv ist. wc -w zählt die Wörter in einer Zeichenkette – das entspricht der Anzahl der Dienstnamen.
Du schreibst ein robustes Backup-Skript, das Voraussetzungen prüft, eine log()-Funktion nutzt und bei Fehlern kontrolliert abbricht.
nano backup.sh. Beginne mit #!/bin/bash und set -euo pipefail. Definiere Variablen: QUELLE="/etc", ZIEL="/tmp/backups", LOGFILE="/tmp/backup.log".log()-Funktion: Sie gibt [Datum Uhrzeit] Nachricht aus und schreibt sie gleichzeitig in $LOGFILE (mit tee -a).$QUELLE existiert (-d). Falls nicht: Fehlermeldung ausgeben und mit exit 1 abbrechen. Erstelle das Zielverzeichnis falls nötig: mkdir -p "$ZIEL".tar -czf "$ZIEL/backup_$(date +%Y%m%d_%H%M%S).tar.gz" "$QUELLE" 2>/dev/null. Logge Erfolg und Dateiname.chmod +x backup.sh && ./backup.sh. Prüfe danach mit ls -lh /tmp/backups/ und cat /tmp/backup.log.Lösungshinweise anzeigen
Das Backup-Skript erstellt eine .tar.gz-Datei mit aktuellem Zeitstempel im Namen. Mit set -euo pipefail bricht es bei jedem unerwarteten Fehler ab. Die log()-Funktion nimmt $1 als Nachricht entgegen und schreibt sie mit Zeitstempel. Wenn du das Quellverzeichnis änderst, teste erst mit echo statt dem echten tar-Befehl, um den Skriptaufbau zu prüfen.
Du erweiterst das Backup-Skript so, dass Quell- und Zielverzeichnis als Argumente übergeben werden – flexibler und für mehrere Zwecke wiederverwendbar.
cp backup.sh backup-flexibel.sh. Öffne es mit nano backup-flexibel.sh.QUELLE="$1" und ZIEL="$2". Das Skript liest jetzt Quell- und Zielverzeichnis aus den übergebenen Argumenten.$# -lt 2), gib eine Hilfsmeldung aus und brich ab: echo "Nutzung: $0 QUELLE ZIEL" und exit 1. Platziere diese Prüfung direkt nach dem Shebang und set-Optionen../backup-flexibel.sh – es sollte die Hilfsmeldung anzeigen. Dann mit Argumenten: ./backup-flexibel.sh /etc /tmp/test-backup.ls -lh /tmp/test-backup/.Lösungshinweise anzeigen
Die Prüfung if [ $# -lt 2 ] fängt fehlende Argumente ab. $0 enthält den Skriptnamen – nützlich für verständliche Hilfsmeldungen. Die Argumentprüfung muss vor dem Zugriff auf $1 und $2 stehen, damit set -u nicht vorher wegen undefinierter Variable abbricht.
Linux-Services & Cron
Dienste starten, stoppen, überwachen und zeitgesteuerte Aufgaben einrichten – damit dein Server rund um die Uhr automatisch arbeitet. Du lernst systemd von Grund auf und richtest eigene Services und Timer ein.
Alle Befehle in diesem Modul laufen auf dem Server – du bist per SSH eingeloggt (Modul 02). Auf deinem eigenen Rechner tippst du nur den SSH-Login. Alle Code-Blöcke sind mit der blauen Server-Zone markiert.
systemd ist der zentrale Manager für alle Dienste auf deinem Linux-Server. Er startet Dienste beim Hochfahren, überwacht sie und startet sie bei Abstürzen neu. Stell dir systemd wie einen Schichtleiter vor: Er weiß, welche Mitarbeiter (Dienste) zu welchem Zeitpunkt eingesetzt werden müssen, in welcher Reihenfolge sie anfangen sollen – und was zu tun ist, wenn einer ausfällt.
Jeder Dienst wird durch eine Unit-Datei beschrieben – eine kleine Textdatei, die erklärt, was der Dienst tut und wie er gestartet werden soll. Unit-Dateien liegen unter /etc/systemd/system/ (eigene) oder /lib/systemd/system/ (vom System installiert).
Units und Targets
systemd unterscheidet verschiedene Unit-Typen. Die wichtigsten für den Alltag:
| Unit-Typ | Dateiendung | Verwendung |
|---|---|---|
| Service | .service | Hintergrunddienste (nginx, ssh, mysql) |
| Timer | .timer | Zeitgesteuerte Aufgaben (moderne Cron-Alternative) |
| Mount | .mount | Dateisystem-Einhängepunkte |
| Target | .target | Gruppen von Units – steuern die Boot-Reihenfolge |
Ein Target bündelt mehrere Units. Das wichtigste Target ist multi-user.target – das entspricht dem normalen Serverbetrieb ohne grafische Oberfläche. Wenn du einen Dienst mit WantedBy=multi-user.target einrichtest, startet er beim normalen Hochfahren automatisch mit.
Mit systemctl steuerst du alle Dienste auf dem Server. Das ist dein täglich genutztes Werkzeug – vergleichbar mit dem Gaspedal, der Bremse und dem Tacho deines Servers.
Dienst starten, stoppen, neu starten und Status prüfen:
# Dienst starten:
sudo systemctl start nginx
# Dienst stoppen:
sudo systemctl stop nginx
# Dienst neu starten (Stop + Start):
sudo systemctl restart nginx
# Konfiguration neu laden, ohne den Dienst zu stoppen:
sudo systemctl reload nginx
# Status und letzte Logs anzeigen:
systemctl status nginx Autostart beim Boot steuern und Übersicht behalten:
# Autostart aktivieren (startet ab nächstem Boot):
sudo systemctl enable nginx
# Autostart aktivieren und JETZT sofort starten:
sudo systemctl enable --now nginx
# Autostart deaktivieren:
sudo systemctl disable nginx
# Dienst komplett sperren (kann nicht mehr gestartet werden):
sudo systemctl mask nginx
# Sperre aufheben:
sudo systemctl unmask nginx
# Alle laufenden Dienste anzeigen:
systemctl list-units --type=service --state=running
# Alle fehlgeschlagenen Dienste:
systemctl --failed | Befehl | Was er tut | Wann verwenden |
|---|---|---|
start | Dienst sofort starten | Manuell einschalten |
stop | Dienst sofort stoppen | Manuell ausschalten |
restart | Stop + Start | Nach Konfigurationsänderung |
reload | Konfig neu laden, Dienst läuft weiter | nginx/Apache nach Config-Änderung |
enable | Autostart aktivieren | Dienst soll nach Boot automatisch laufen |
disable | Autostart deaktivieren | Dienst nur manuell starten |
mask | Dienst komplett sperren | Dienst darf auf keinen Fall starten |
status | Status + letzte Logs anzeigen | Diagnose, Fehlersuche |
reload liest die Konfigurationsdatei neu, ohne den Dienst zu stoppen – bestehende Verbindungen bleiben erhalten. restart stoppt und startet komplett neu – alle Verbindungen werden kurz unterbrochen. Bei nginx oder Apache nutze reload wann immer möglich.
Du kannst eigene Dienste erstellen – zum Beispiel für dein Bash-Skript aus Modul 21, eine Webanwendung oder einen Monitoring-Prozess. Ein Dienst besteht aus einer einzigen Textdatei mit drei Abschnitten: [Unit] (was ist das?), [Service] (wie starten?) und [Install] (wann automatisch starten?).
Erstelle zuerst die Unit-Datei. Der Dateiname bestimmt später den Dienstnamen:
[Unit]
Description=Mein eigener Dienst
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/meine-app
ExecStart=/opt/meine-app/start.sh
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target Danach systemd die neue Datei bekanntmachen und den Dienst starten:
# systemd über die neue Unit informieren (immer nach Änderungen!):
sudo systemctl daemon-reload
# Dienst starten und Status prüfen:
sudo systemctl start mein-dienst
systemctl status mein-dienst
# Wenn alles läuft: Autostart aktivieren:
sudo systemctl enable mein-dienst | Sektion | Feld | Bedeutung |
|---|---|---|
[Unit] | Description= | Kurze Beschreibung (erscheint in systemctl status) |
[Unit] | After= | Erst starten, wenn dieser andere Dienst läuft (Abhängigkeit) |
[Service] | Type=simple | Hauptprozess ist der gestartete Befehl direkt |
[Service] | User= | Unter welchem Benutzer der Dienst läuft |
[Service] | ExecStart= | Befehl zum Starten – immer vollständiger Pfad! |
[Service] | Restart=on-failure | Bei Absturz automatisch neu starten |
[Install] | WantedBy= | In welchem Target (Boot-Stufe) automatisch starten |
Eine syntaktisch fehlerhafte Unit-Datei oder ein Dienst, der beim Start hängt, kann den Server-Boot stark verlangsamen oder unterbrechen. Teste neue Dienste immer zuerst manuell mit systemctl start und prüfe den Status mit systemctl status, bevor du enable aktivierst. Im Zweifel: Type=simple verwenden und den Start-Befehl vorher direkt auf der Kommandozeile testen.
systemd-Unit-Dateien haben viele Optionen und die Syntax muss exakt stimmen. Beschreibe deinen Fall konkret: „Erstelle mir eine systemd-Unit-Datei für ein Python-Skript unter /opt/monitoring/check.py, das unter dem Benutzer 'monitoring' laufen soll, auf das Netzwerk warten muss und bei Fehlern nach 10 Sekunden 3-mal neu starten soll. Logs sollen ins Journal."
systemd speichert alle Dienst-Logs zentral im Journal. Mit journalctl liest du sie aus – strukturiert, filterbar und viel übersichtlicher als in einzelnen Log-Dateien zu wühlen. Das Journal ist dein erster Anlaufpunkt bei Problemen.
# Alle Logs eines bestimmten Dienstes:
sudo journalctl -u nginx
# Nur die letzten 50 Zeilen:
sudo journalctl -u nginx -n 50
# Live-Modus – neue Zeilen erscheinen sofort (Strg+C zum Beenden):
sudo journalctl -u nginx -f
# Logs seit heute:
sudo journalctl -u nginx --since today
# Logs aus einem Zeitfenster:
sudo journalctl -u nginx --since "2026-03-14 10:00" --until "2026-03-14 11:00"
# Nur Fehlermeldungen (Priorität err und schlimmer):
sudo journalctl -u nginx -p err
# Alle Dienste, nur Fehler, seit dem letzten Boot:
sudo journalctl -p err -b
# Speicherplatz des Journals prüfen:
sudo journalctl --disk-usage
# Alte Logs aufräumen (Journal auf 500 MB begrenzen):
sudo journalctl --vacuum-size=500M Der -f-Parameter (follow) zeigt neue Log-Zeilen in Echtzeit – genau wie tail -f logdatei aus Modul 03. Unverzichtbar beim Debuggen: Dienst in einem Terminal neu starten, in einem anderen Terminal journalctl -u dienst -f laufen lassen und zusehen, was passiert.
Cron ist wie ein Terminkalender für deinen Server: Du trägst ein, wann was passieren soll, und der Server hält sich daran – egal ob du schläfst oder Urlaub hast. Perfekt für Backups, Updates, Log-Bereinigung und Überwachungsskripte.
Die Crontab bearbeiten
# Eigene Crontab öffnen und bearbeiten (öffnet einen Editor):
crontab -e
# Aktuelle Crontab anzeigen:
crontab -l
# Crontab eines anderen Benutzers anzeigen (als root):
sudo crontab -u www-data -l Die Cron-Syntax verstehen
Jede Zeile in der Crontab folgt diesem Muster – fünf Zeitfelder, dann der Befehl:
# ┌───────── Minute (0–59)
# │ ┌─────── Stunde (0–23)
# │ │ ┌───── Tag (1–31)
# │ │ │ ┌─── Monat (1–12)
# │ │ │ │ ┌─ Wochentag (0–7, 0 und 7 = Sonntag)
# │ │ │ │ │
# * * * * * Befehl
# Täglich um 03:00 Uhr:
0 3 * * * /home/user/backup.sh
# Alle 15 Minuten:
*/15 * * * * /home/user/check.sh
# Jeden Sonntag um Mitternacht:
0 0 * * 0 /home/user/weekly-report.sh
# Um 06:00 und 18:00 Uhr:
0 6,18 * * * /home/user/sync.sh
# Am 1. jeden Monats um 02:30 Uhr:
30 2 1 * * /home/user/monats-backup.sh Ausgabe immer in eine Log-Datei umleiten – sonst siehst du nichts von der Ausführung:
# Ausgabe und Fehler in Log-Datei schreiben:
0 3 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1
# Ausgabe unterdrücken, nur Fehler protokollieren:
*/5 * * * * /home/user/check.sh > /dev/null 2>&1 | Zeichen | Bedeutung | Beispiel |
|---|---|---|
* | Jeder mögliche Wert | * * * * * = jede Minute |
*/n | Alle n Einheiten | */5 * * * * = alle 5 Minuten |
a,b | Bestimmte Werte | 0 6,18 * * * = 6 und 18 Uhr |
a-b | Bereich | 0 9-17 * * 1-5 = stündlich 9–17 Uhr, Mo–Fr |
In der Crontab hat % eine Sonderbedeutung (Zeilenumbruch im Befehl). Wenn du date +%Y%m%d in einem Cron-Job nutzt, musst du jedes Prozentzeichen escapen: date +\%Y\%m\%d. Einfacher: Schreibe den Befehl in ein Skript und rufe das Skript per Cron auf – dann gilt das normale Shell-Verhalten ohne diese Falle.
Die Cron-Syntax verwirrt am Anfang. Nutze crontab.guru im Browser: Trage dort die fünf Zeitfelder ein und siehst sofort auf Deutsch, wann der Job laufen wird – sehr hilfreich zum Testen und Überprüfen.
systemd-Timer bieten gegenüber Cron einige Vorteile: bessere Logs (alles im Journal), Abhängigkeiten zu anderen Diensten, und Persistent=true holt verpasste Ausführungen nach einem Neustart automatisch nach. Für neue Setups sind Timer oft die bessere Wahl.
Ein Timer braucht immer zwei Dateien: die Timer-Datei (wann?) und eine Service-Datei (was?):
[Unit]
Description=Taegliches Backup – jeden Tag um 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target [Unit]
Description=Taegliches Backup
[Service]
Type=oneshot
User=backup
ExecStart=/home/backup/backup.sh Timer laden, starten und prüfen:
sudo systemctl daemon-reload
# Timer aktivieren und sofort starten:
sudo systemctl enable --now backup.timer
# Alle aktiven Timer und nächste Ausführungszeit anzeigen:
systemctl list-timers
# Timer-Status prüfen:
systemctl status backup.timer
# Zugehörigen Service manuell testen:
sudo systemctl start backup.service
journalctl -u backup.service -n 20 | Timer-Feld | Bedeutung | Beispiel |
|---|---|---|
OnCalendar= | Fester Zeitplan (wie Cron) | Mon *-*-* 02:00:00 = montags um 2 Uhr |
OnBootSec= | X Zeit nach dem Boot ausführen | OnBootSec=5min |
OnUnitActiveSec= | Regelmäßig, relativ zur letzten Ausführung | OnUnitActiveSec=1h |
Persistent=true | Verpasste Ausführung nach Neustart nachholen | Wichtig für Backup-Timer |
Du hast einen Cron-Job, der nicht zuverlässig läuft, oder willst auf systemd-Timer umsteigen? Zeig der KI deinen bestehenden Crontab-Eintrag: „Ich habe diesen Cron-Job: '0 3 * * * /home/user/backup.sh'. Schreibe mir dafür eine systemd-Timer- und Service-Unit-Datei. Der Dienst soll unter dem Benutzer 'backup' laufen und verpasste Ausführungen nach einem Neustart nachholen."
Während Cron für wiederkehrende Aufgaben gedacht ist, plant at eine Aufgabe einmalig für einen bestimmten Zeitpunkt in der Zukunft. Praktisch für: „Starte diesen Prozess in 30 Minuten" oder „Führe das Update heute Nacht um 02:00 aus".
# at installieren (auf Debian/Ubuntu oft nicht vorinstalliert):
sudo apt install at
# Skript in 30 Minuten ausführen:
echo "/home/user/update.sh" | at now + 30 minutes
# Befehl um 02:00 Uhr heute Nacht (als root für systemctl):
echo "systemctl reboot" | sudo at 02:00
# Alle geplanten at-Aufgaben anzeigen:
atq
# Geplante Aufgabe löschen – Nummer aus atq entnehmen:
atrm 3 Lerne die wichtigsten systemctl-Befehle kennen, indem du bereits laufende Dienste auf deinem Server untersuchst.
systemctl list-units --type=service --state=running. Wie viele Dienste laufen gerade auf dem Server?systemctl status ssh. Notiere: Ist er aktiv? Seit wann läuft er? Was steht in der letzten Log-Zeile?systemctl is-enabled cron. Die Antwort sollte enabled sein.systemctl --failed. Gibt es Einträge? Falls ja, untersuche einen davon mit journalctl -u DIENSTNAME -n 30.sudo journalctl -u ssh -n 20. Erkennst du deinen eigenen Login in den Einträgen?Lösungshinweise anzeigen
systemctl status ssh zeigt den Dienst als active (running). PID, Speicherverbrauch und die letzten Log-Zeilen sind direkt sichtbar – kein separates journalctl nötig für eine schnelle Übersicht.
systemctl --failed sollte auf einem frisch eingerichteten Server leer sein. Jeder Eintrag dort ist ein Hinweis auf ein Problem, das untersucht werden sollte.
Im Journal von SSH erkennst du deinen Login an Zeilen wie Accepted publickey for admin from 192.168.x.x.
Du erstellst einen eigenen Dienst, der ein Bash-Skript dauerhaft laufen lässt – und siehst, wie systemd es überwacht und im Journal protokolliert.
/opt/hello-service.sh. Erste Zeile: #!/bin/bash. Zweite Zeile: while true; do echo "$(date): Service laeuft"; sleep 10; done. Die Schleife gibt alle 10 Sekunden eine Zeile aus – das Journal fängt sie auf.sudo chmod +x /opt/hello-service.sh. Starte es kurz im Hintergrund zum Testen: /opt/hello-service.sh &. Warte 15 Sekunden, prüfe die Ausgabe mit jobs, dann stoppe den Hintergrundprozess wieder: kill %1 (beendet Job Nr. 1 aus der jobs-Liste)./etc/systemd/system/hello.service. Nutze das Beispiel aus dem Modul: Type=simple, ExecStart=/opt/hello-service.sh, Restart=on-failure, WantedBy=multi-user.target. Trage als User= deinen eigenen Benutzernamen ein (nicht root).sudo systemctl daemon-reload, dann sudo systemctl start hello. Prüfe den Status: systemctl status hello.journalctl -u hello -f (Strg+C zum Beenden). Stoppe den Dienst danach sauber: sudo systemctl stop hello.Lösungshinweise anzeigen
Nach systemctl start hello zeigt systemctl status hello den Dienst als active (running). PID und Laufzeit sind sichtbar.
journalctl -u hello -f zeigt die Ausgaben des Skripts in Echtzeit – jede Zeile, die das Skript ausgibt, landet im Journal.
Wenn der Status failed zeigt: journalctl -u hello -n 20 lesen – meistens ist ein Tippfehler im Pfad oder fehlende Ausführberechtigung (chmod +x vergessen) die Ursache.
Du richtest denselben automatischen Job einmal als Cron-Job und einmal als systemd-Timer ein – so siehst du beide Methoden im direkten Vergleich.
/home/user/disk-check.sh mit zwei Zeilen: #!/bin/bash und df -h / >> /tmp/disk-check.log. Mache es ausführbar und teste es manuell: /home/user/disk-check.sh – prüfe danach cat /tmp/disk-check.log.crontab -e. Füge einen Eintrag hinzu, der das Skript alle 5 Minuten ausführt: */5 * * * * /home/user/disk-check.sh. Speichere und schließe den Editor.crontab -l. Warte 5 Minuten und schau ob die Log-Datei neue Einträge enthält: tail /tmp/disk-check.log./etc/systemd/system/disk-check.service an (Type=oneshot, ExecStart=/home/user/disk-check.sh) und /etc/systemd/system/disk-check.timer (OnCalendar=*:0/5 für alle 5 Minuten, Persistent=true).sudo systemctl daemon-reload, dann sudo systemctl enable --now disk-check.timer. Prüfe mit systemctl list-timers, wann die nächste Ausführung geplant ist.Lösungshinweise anzeigen
Der Crontab-Eintrag für alle 5 Minuten: */5 * * * * /home/user/disk-check.sh. Cron-Aktivität im Syslog prüfen: grep CRON /var/log/syslog | tail
OnCalendar=*:0/5 im Timer bedeutet: jede Stunde, jede durch 5 teilbare Minute. systemctl list-timers zeigt in der Spalte NEXT, wann der Timer das nächste Mal feuert.
Der Unterschied im Alltag: Beim systemd-Timer siehst du alle Logs direkt mit journalctl -u disk-check.service – kein separates Log-File nötig, alles zentral im Journal.
Backups
Ein Backup ist wie eine Versicherung: Du merkst es erst, wenn du es brauchst. Wer keines hat, zahlt zweimal -- mit Datenverlust und Wiederherstellungsaufwand. In diesem Modul lernst du, wie du mit tar, rsync und automatischen Skripten zuverlässige Backups aufbaust.
In diesem Modul arbeitest du ausschließlich auf dem Server -- du bist bereits per SSH eingeloggt (Modul 02). Alle Befehle, Skripte und Konfigurationsdateien liegen auf dem Server. Die Legende zur Erinnerung:
Alle Befehle in diesem Modul laufen auf dem Server -- du bist bereits per SSH eingeloggt.
Die goldene Regel für Backups lässt sich einfach merken. Stell dir vor, dein Werkzeugschrank steht in der Werkstatt. Wenn die Werkstatt abbrennt, sind alle Werkzeuge weg -- es sei denn, du hast Ersatz an einem anderen Ort.
3 Kopien deiner Daten (Original + 2 Backups), auf 2 verschiedenen Medien (z.B. interne SSD + externe Festplatte), davon 1 an einem anderen Standort (z.B. Cloud-Speicher, zweiter Server). Wenn dein Serverraum abbrennt, bist du trotzdem abgesichert.
| Backup-Strategie | Was wird gespeichert | Vorteil | Nachteil |
|---|---|---|---|
| Vollbackup | Alle Daten komplett | Einfache Wiederherstellung | Braucht viel Speicher |
| Inkrementell | Nur Änderungen seit letztem Backup | Schnell, wenig Speicher | Alle Inkremente nötig für Restore |
| Differenziell | Alle Änderungen seit letztem Vollbackup | Schnellerer Restore | Wird mit der Zeit größer |
| Deduplizierung | Gleiche Datenblöcke nur einmal | Extrem speichereffizient | Abhängig vom Tool (z.B. borgbackup) |
Wenn du /var/www nach /backup sicherst und beide Verzeichnisse auf derselben Festplatte liegen, verlierst du bei einem Festplattendefekt alles auf einmal. Das ist kein Backup -- das ist eine Kopie. Ein echtes Backup liegt auf einem anderen physischen Gerät oder Speicherort: externe Festplatte, NAS, USB-Stick, Cloud-Speicher oder zweiter Server.
tar (Tape Archiver) ist das klassische Werkzeug zum Packen und Entpacken von Dateien auf Linux. Es fasst viele Dateien in eine einzige Archivdatei zusammen und kann sie dabei komprimieren. Denk daran wie ein Umzugskarton: Alles kommt rein, beschriftet und verschlossen.
Die wichtigsten tar-Befehle im Überblick:
# Verzeichnis als komprimiertes Archiv sichern (gzip):
tar -czf /backup/www-backup.tar.gz /var/www
# -c = create (erstellen)
# -z = gzip-Komprimierung
# -f = Dateiname des Archivs (kommt gleich danach)
# Mit Zeitstempel im Dateinamen:
tar -czf /backup/www-$(date +%Y-%m-%d).tar.gz /var/www
# Mehrere Verzeichnisse sichern:
tar -czf /backup/server-backup.tar.gz /var/www /etc /home # Inhalt des Archivs anzeigen (ohne zu entpacken):
tar -tzf /backup/www-backup.tar.gz
# Archiv entpacken (ins aktuelle Verzeichnis):
tar -xzf /backup/www-backup.tar.gz
# Archiv in ein bestimmtes Verzeichnis entpacken:
tar -xzf /backup/www-backup.tar.gz -C /tmp/restore/ | Option | Bedeutung |
|---|---|
-c | Archiv erstellen (create) |
-x | Archiv entpacken (extract) |
-t | Inhalt anzeigen (list) |
-z | gzip-Komprimierung (.tar.gz) |
-j | bzip2-Komprimierung (.tar.bz2, kleiner aber langsamer) |
-f | Dateiname des Archivs (immer am Ende der Optionen) |
-v | Dateien beim Packen anzeigen (verbose) |
-C | Zielverzeichnis beim Entpacken |
Merke dir: c = create, x = extract. Die Optionen zf bleiben gleich. Also: tar -czf archiv.tar.gz ordner/ zum Packen und tar -xzf archiv.tar.gz zum Entpacken.
rsync kopiert Dateien intelligent: Es überträgt nur die Unterschiede zwischen Quelle und Ziel. Das spart Zeit und Bandbreite -- perfekt für tägliche Backups, weil nach dem ersten Durchlauf nur noch geänderte Dateien kopiert werden. Stell dir vor, du bringst nur die Teile mit zur Werkstatt, die wirklich erneuert werden müssen -- nicht den kompletten Wagen.
Grundlegende rsync-Befehle für lokale und entfernte Backups:
# Lokales Backup (Verzeichnis → Backup-Festplatte):
rsync -avh /var/www/ /mnt/backup/www/
# -a = Archivmodus (Berechtigungen, Zeitstempel, Symlinks erhalten)
# -v = verbose (Fortschritt anzeigen)
# -h = human-readable (Größen in KB/MB/GB)
# Auf entfernten Backup-Server senden (per SSH):
rsync -avhz /var/www/ backup-user@192.168.1.100:/backup/www/
# -z = Daten während der Übertragung komprimieren
# Trockenlauf -- zeigt was passieren würde, ohne etwas zu ändern:
rsync -avhn /var/www/ /mnt/backup/www/
# Gelöschte Dateien im Backup auch löschen (Spiegel):
rsync -avh --delete /var/www/ /mnt/backup/www/
# Bestimmte Dateitypen ausschließen:
rsync -avh --exclude '*.log' --exclude 'cache/' /var/www/ /mnt/backup/www/ | Flag | Bedeutung |
|---|---|
-a | Archivmodus: Berechtigungen, Zeitstempel, Symlinks erhalten |
-v | Verbose: zeigt übertragene Dateien an |
-h | Größen in KB/MB/GB (human-readable) |
-z | Komprimierung während der Übertragung |
-n | Trockenlauf: zeigt was passieren würde |
--delete | Im Ziel löschen, was in der Quelle nicht mehr existiert |
--exclude | Dateien oder Verzeichnisse ausschließen |
--progress | Fortschrittsbalken pro Datei anzeigen |
/var/www/ (mit Schrägstrich) kopiert den Inhalt des Ordners. /var/www (ohne Schrägstrich) kopiert den Ordner selbst. Das klingt klein, macht aber einen großen Unterschied: Mit Slash landen alle Dateien direkt im Ziel, ohne Slash entsteht ein Unterordner www/ im Ziel.
Aus Modul 21 (Bash-Scripting) und Modul 22 (Services & Cron) kennst du bereits Skripte und Cron-Jobs. Hier kombinierst du beides: ein Backup-Skript, das automatisch täglich läuft.
Erstelle das Skript unter /usr/local/bin/backup-daily.sh:
#!/bin/bash
set -euo pipefail
# Konfiguration -- hier anpassen:
QUELLE="/var/www"
ZIEL="/mnt/backup/www"
LOGFILE="/var/log/backup-daily.log"
# Backup-Ziel anlegen falls nicht vorhanden:
mkdir -p "$ZIEL"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup gestartet" >> "$LOGFILE"
rsync -avh --delete \
--exclude '*.log' \
--exclude 'cache/' \
"$QUELLE/" "$ZIEL/" >> "$LOGFILE" 2>&1
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup abgeschlossen" >> "$LOGFILE" Skript ausführbar machen und Cron-Job einrichten:
# Skript ausführbar machen:
sudo chmod +x /usr/local/bin/backup-daily.sh
# Cron-Job für root einrichten (täglich um 3 Uhr):
sudo crontab -e
# Folgende Zeile hinzufügen:
0 3 * * * /usr/local/bin/backup-daily.sh
# Log-Datei auf Fehler prüfen:
tail -20 /var/log/backup-daily.log Dein Server hat andere Verzeichnisse, vielleicht eine Datenbank, vielleicht mehrere Backup-Ziele. Beschreibe dein Setup und lass dir das Skript anpassen: „Ich habe einen Ubuntu-Server mit /var/www (WordPress), /home/user und einer MariaDB-Datenbank. Schreibe mir ein Bash-Backup-Skript, das täglich rsync und mysqldump ausführt, alles nach /mnt/nas/backup/ sichert und mir bei Fehler eine E-Mail schickt."
Dateien und Verzeichnisse sicherst du mit tar oder rsync. Datenbanken wie MariaDB oder MySQL funktionieren aber anders -- ihre Daten liegen in einem internen Format, das nicht einfach kopiert werden kann, während die Datenbank läuft. Dafür gibt es mysqldump. Mehr Details zu Datenbanken lernst du in Modul 16.
# Einzelne Datenbank sichern:
mysqldump -u root -p meine_datenbank > /backup/meine_datenbank-$(date +%Y-%m-%d).sql
# Alle Datenbanken sichern:
mysqldump -u root -p --all-databases > /backup/alle-datenbanken-$(date +%Y-%m-%d).sql
# Backup komprimieren (spart Speicher):
mysqldump -u root -p meine_datenbank | gzip > /backup/db-$(date +%Y-%m-%d).sql.gz
# Datenbank wiederherstellen:
mysql -u root -p meine_datenbank < /backup/meine_datenbank-2026-03-14.sql Damit das Skript automatisch läuft, ohne nach einem Passwort zu fragen, kannst du eine Credentials-Datei anlegen: /root/.my.cnf mit dem Inhalt [client], user=root, password=deinPasswort. Die Datei muss Rechte 600 haben: chmod 600 /root/.my.cnf. Dann entfällt -p im Befehl.
Ein Backup, das neben dem Original liegt, schützt nur vor versehentlichem Löschen -- nicht vor Hardwareausfall, Brand oder Diebstahl. Die 3-2-1-Regel verlangt mindestens einen Standort außerhalb.
| Speicherort | Geeignet für | Hinweis |
|---|---|---|
| Externe USB-Festplatte | Kleinere Server, lokale Backups | Muss dauerhaft angeschlossen oder regelmäßig getauscht werden |
| NAS im Netzwerk (NFS/SAMBA) | Heimnetz, kleine Firmen | NFS/SAMBA mounten (Modul 12), dann rsync darauf |
| Zweiter Server (rsync per SSH) | Produktionsserver | rsync direkt per SSH, kein Passwort nötig mit SSH-Keys (Modul 02) |
| Cloud-Speicher (S3, Backblaze) | Offsite-Backup, 3-2-1-Pflicht | Tools wie rclone synchronisieren Ordner mit Cloud-Diensten |
| borgbackup auf Remote-Server | Verschlüsseltes Offsite-Backup | Dedupliziert, verschlüsselt, platzsparend -- moderne Alternative |
Bevor du auf eine externe Festplatte sichern kannst, muss sie eingebunden sein (Modul 10):
# Backup-Mountpoint anlegen:
sudo mkdir -p /mnt/backup
# USB-Festplatte einbinden:
sudo mount /dev/sdb1 /mnt/backup
# Prüfen ob Platz vorhanden:
df -h /mnt/backup
# Für dauerhaftes Einbinden beim Start: /etc/fstab (Modul 10) borgbackup ist eine moderne Alternative zu tar und rsync für Backups. Es dedupliziert Daten (gleiche Blöcke werden nur einmal gespeichert), komprimiert automatisch und verschlüsselt optional. Statt täglich 50 GB neu zu kopieren, speichert Borg oft nur wenige MB für jede neue Version.
# Borg installieren:
sudo apt install borgbackup
# Repository initialisieren (mit Verschlüsselung):
borg init --encryption=repokey /mnt/backup/borg-repo
# Backup erstellen (Archivname mit Datum):
borg create --stats /mnt/backup/borg-repo::server-$(date +%Y-%m-%d) /var/www /etc
# Alle Backups auflisten:
borg list /mnt/backup/borg-repo
# Alte Backups aufräumen (7 täglich, 4 wöchentlich, 6 monatlich behalten):
borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 /mnt/backup/borg-repo Wenn du bei borg init eine Passphrase wählst, sind deine Backups ohne diese Passphrase nicht wiederherstellbar. Exportiere den Schlüssel und speichere ihn außerhalb des Servers: borg key export /mnt/backup/borg-repo borg-key.txt -- danach die Datei herunterladen und in einem Passwort-Manager oder Tresor aufbewahren.
Ein Backup, das du nie getestet hast, ist kein Backup -- es ist Hoffnung. Der einzige Beweis, dass ein Backup funktioniert, ist die erfolgreiche Wiederherstellung. Plane mindestens einmal im Monat einen Restore-Test ein.
# tar-Archiv prüfen (Inhalt ohne Entpacken anzeigen):
tar -tzf /backup/www-backup.tar.gz | head -20
# tar-Archiv in Testverzeichnis entpacken:
mkdir -p /tmp/restore-test
tar -xzf /backup/www-backup.tar.gz -C /tmp/restore-test/
# Inhalt des Restore-Verzeichnisses anzeigen:
ls -la /tmp/restore-test/
# rsync-Backup prüfen -- Inhalt anzeigen:
ls -lh /mnt/backup/www/
# Einzelne Datei aus rsync-Backup zurückkopieren:
cp /mnt/backup/www/index.php /var/www/html/index.php Stelle Daten immer zuerst in ein temporäres Verzeichnis wieder her (/tmp/restore-test/). Dort kannst du prüfen, ob alles vollständig ist, bevor du die Dateien ins echte System kopierst. Ein fehlerhafter Restore überschreibt sonst vielleicht funktionierende Daten.
Jeder Server hat andere Anforderungen. Beschreibe dein konkretes Setup und lass dir eine passende Strategie erstellen: „Ich betreibe einen Ubuntu-Server mit WordPress (10 GB /var/www), MariaDB-Datenbanken und E-Mails in /home. Ich habe eine externe Festplatte unter /mnt/backup und möchte zusätzlich ein Offsite-Backup. Erstelle mir einen vollständigen Backup-Plan nach der 3-2-1-Regel mit Skripten und Cron-Jobs."
Du erstellst ein tar-Archiv von einem Verzeichnis und stellst es in einem Testverzeichnis wieder her. Das trainiert den kompletten Backup-und-Restore-Zyklus.
mkdir -p ~/test-daten && echo "Datei1" > ~/test-daten/datei1.txt && echo "Datei2" > ~/test-daten/datei2.txttar -czf /tmp/test-backup.tar.gz ~/test-daten/tar -tzf /tmp/test-backup.tar.gz. Du siehst die gespeicherten Pfade -- merke dir die ersten zwei Zeilen.mkdir -p /tmp/restore-test && tar -xzf /tmp/test-backup.tar.gz -C /tmp/restore-test/ls -la /tmp/restore-test/ und dann mit dem gesehenen Pfad weiter navigieren: ls -la /tmp/restore-test/root/test-daten/ (als root-User) oder ls -la /tmp/restore-test/home/DEINUSER/test-daten/ (als normaler User).Lösungshinweise anzeigen
Bei tar -tzf siehst du die gespeicherten Pfade -- tar speichert den Pfad relativ zum Wurzelverzeichnis, also z.B. root/test-daten/datei1.txt als root-User. Beim Entpacken nach /tmp/restore-test/ entsteht dieser Pfad dort vollständig. Mit ls -la /tmp/restore-test/ siehst du zuerst das Nutzerverzeichnis und kannst dann in test-daten/ navigieren.
Du richtest ein inkrementelles Backup mit rsync ein und erlebst live, wie rsync beim zweiten Durchlauf nur geänderte Dateien überträgt.
mkdir -p /tmp/backup-zielrsync -avh ~/test-daten/ /tmp/backup-ziel/. Beide Dateien werden übertragen.echo "geaendert" >> ~/test-daten/datei1.txtrsync -avh ~/test-daten/ /tmp/backup-ziel/. Beobachte: Nur die geänderte Datei wird diesmal übertragen.--delete durch: rsync -avhn --delete ~/test-daten/ /tmp/backup-ziel/. Der Trockenlauf zeigt was passieren würde -- ohne etwas zu verändern.cp /tmp/backup-ziel/datei2.txt ~/wiederhergestellt.txt und prüfe den Inhalt: cat ~/wiederhergestellt.txtLösungshinweise anzeigen
Beim zweiten rsync-Lauf zeigt die Ausgabe nur datei1.txt -- die anderen Dateien sind unverändert und werden übersprungen. Das ist das inkrementelle Prinzip: rsync vergleicht Größe und Zeitstempel, nicht den Dateiinhalt. Der Trockenlauf (-n) ist immer eine gute Idee, bevor du --delete zum ersten Mal einsetzt -- --delete entfernt Dateien im Ziel, die in der Quelle nicht mehr existieren.
Du erstellst ein Backup-Skript und richtest einen Cron-Job ein, der es täglich ausführt. Damit läuft dein Backup automatisch -- ohne dass du daran denken musst.
sudo nano /usr/local/bin/backup-daily.sh und füge den Skript-Inhalt aus dem Abschnitt "Automatisches Backup-Skript" ein. Passe QUELLE und ZIEL an deine Pfade an.sudo chmod +x /usr/local/bin/backup-daily.shsudo /usr/local/bin/backup-daily.sh. Prüfe danach die Log-Datei: tail -20 /var/log/backup-daily.logsudo crontab -e und füge am Ende hinzu: 0 3 * * * /usr/local/bin/backup-daily.sh. Das bedeutet: täglich um 3:00 Uhr ausführen (Modul 22).sudo crontab -l. Du solltest den neuen Eintrag sehen.Lösungshinweise anzeigen
Das Skript schreibt Start- und Endezeit in die Log-Datei. Nach dem manuellen Test siehst du dort zwei Zeilen: [Datum] Backup gestartet und [Datum] Backup abgeschlossen. Falls rsync Fehler meldet, stehen diese ebenfalls in der Log-Datei -- der Eintrag 2>&1 im Skript leitet Fehlermeldungen ins Log um. Das Cron-Muster 0 3 * * * bedeutet: Minute 0, Stunde 3, jeder Tag, jeder Monat, jeder Wochentag.
Sicherheitshärtung
Ein Server im Internet wird ständig angegriffen -- automatisierte Bots scannen rund um die Uhr nach offenen Türen. In diesem Modul lernst du, deinen Server systematisch zu sichern: SSH absichern, Brute-Force-Angriffe blockieren, Benutzerrechte beschränken und Sicherheitslücken aufspüren.
In diesem Modul arbeitest du ausschließlich auf dem Server -- per SSH eingeloggt. Alle Befehle laufen dort. Stelle sicher, dass du bereits verbunden bist, bevor du anfängst.
Stell dir deinen Server wie eine Werkstatt vor. Eine frisch installierte Werkstatt hat viele Türen, die offen stehen könnten, Werkzeuge die jeder benutzen kann, und keine Liste darüber, wer wann was gemacht hat. Sicherheitshärtung bedeutet: Türen schließen, Werkzeuge einschließen und ein Logbuch führen.
Zwei Grundprinzipien gelten als Fundament jeder Härtung:
- Minimalprinzip (Least Privilege): Jeder Benutzer und jeder Dienst bekommt nur genau die Rechte, die er wirklich braucht. Nicht mehr. Ein Webserver muss keine SSH-Schlüssel lesen können.
- Defense in Depth (Mehrschichtschutz): Kein einzelner Schutz ist unfehlbar. Du kombinierst mehrere Schichten: Firewall + SSH-Härtung + fail2ban + Berechtigungen + Updates. Wenn eine Schicht versagt, hält die nächste.
Bots scannen das gesamte Internet in wenigen Minuten. Dein Server wird innerhalb von Stunden nach der Inbetriebnahme angegriffen -- nicht weil du jemand bist, sondern weil du erreichbar bist. Die Maßnahmen in diesem Modul machen deinen Server für diese automatisierten Angriffe uninteressant.
SSH ist das Haupteinfallstor für Angreifer. Aus Modul 02 kennst du die Grundlagen -- hier gehen wir tiefer. Die Konfigurationsdatei des SSH-Servers heißt /etc/ssh/sshd_config und liegt auf dem Server.
Öffne die Konfigurationsdatei mit einem Editor. Bearbeite nur die Zeilen, die unten aufgeführt sind -- der Rest bleibt unverändert:
# Root-Login komplett verbieten:
PermitRootLogin no
# Nur Schlüssel-Anmeldung erlauben (kein Passwort mehr):
PasswordAuthentication no
PubkeyAuthentication yes
# Nur bestimmte Benutzer erlauben (eigene Benutzernamen einsetzen):
AllowUsers admin deploy
# Leere Passwörter verbieten:
PermitEmptyPasswords no
# Login-Timeout verkürzen (30 Sekunden zum Einloggen):
LoginGraceTime 30
# Maximale Fehlversuche pro Verbindung:
MaxAuthTries 3
# Inaktive Verbindungen nach 10 Minuten trennen:
ClientAliveInterval 300
ClientAliveCountMax 2
# Nicht benötigte Weiterleitungen abschalten:
X11Forwarding no Prüfe die Konfiguration auf Syntaxfehler, bevor du den Dienst neu startest:
# Syntax prüfen (zeigt Fehler an, ohne den Dienst zu starten):
sudo sshd -t
# Erst wenn keine Fehler -- SSH neu laden (bestehende Verbindungen bleiben!):
sudo systemctl reload sshd Teste sofort im zweiten Terminal, ob du dich noch verbinden kannst.
Bevor du PasswordAuthentication no setzt: Öffne ein zweites Terminal und verbinde dich parallel mit deinem SSH-Schlüssel. Erst wenn das funktioniert, deaktiviere das Passwort. Wenn du dich aussperrst, kommst du -- vor allem bei Cloud-Servern ohne Konsolenzugang -- nicht mehr rein.
Setze PermitRootLogin niemals auf yes. Direkter Root-Login über SSH ist ein enormes Sicherheitsrisiko -- ein erfolgreicher Angriff gibt dem Angreifer sofort volle Kontrolle. Melde dich immer als normaler Benutzer an und nutze sudo für administrative Aufgaben.
Brute-Force bedeutet: ein Bot versucht tausende Passwörter in kurzer Zeit. Fail2ban beobachtet die Logdateien und sperrt IP-Adressen automatisch, wenn sie zu viele Fehlversuche produzieren. Wie ein Türsteher, der jemanden nach drei falschen Versuchen rauswirft.
Fail2ban installieren und aktivieren:
sudo apt update
sudo apt install fail2ban
# Dienst starten und Autostart aktivieren:
sudo systemctl enable --now fail2ban
# Status prüfen:
sudo systemctl status fail2ban Fail2ban wird über sogenannte "Jails" konfiguriert. Jede Jail überwacht einen bestimmten Dienst. Die lokale Konfiguration legst du in einer eigenen Datei an -- so werden deine Einstellungen nicht durch Updates überschrieben:
[DEFAULT]
# Wie lange wird eine IP gesperrt (in Sekunden, hier: 1 Stunde):
bantime = 3600
# Wie lange wird das Zeitfenster beobachtet:
findtime = 600
# Wie viele Fehlversuche bis zur Sperre:
maxretry = 5
# Deine eigene IP niemals sperren (anpassen!):
ignoreip = 127.0.0.1/8 192.168.1.0/24
[sshd]
# SSH-Schutz aktivieren:
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 3 Nach dem Speichern den Dienst neu starten und prüfen:
sudo systemctl restart fail2ban
# Status der SSH-Jail prüfen:
sudo fail2ban-client status sshd
# Alle aktiven Jails anzeigen:
sudo fail2ban-client status
# Eine gesperrte IP manuell entsperren:
sudo fail2ban-client set sshd unbanip 1.2.3.4 | Parameter | Bedeutung | Empfohlener Wert |
|---|---|---|
bantime | Sperrdauer in Sekunden | 3600 (1 Stunde) |
findtime | Beobachtungszeitraum in Sekunden | 600 (10 Minuten) |
maxretry | Fehlversuche bis zur Sperre | 3--5 |
ignoreip | IPs die niemals gesperrt werden | Eigenes Netz eintragen |
Trage dein eigenes Heimnetz oder die IP deines Rechners in ignoreip ein. Sonst sperrst du dich selbst aus, wenn du dich dreimal vertippst. Typisches Heimnetz: 192.168.1.0/24 oder 192.168.0.0/24.
Bekannte Sicherheitslücken werden täglich gefunden und gepatcht. Wenn dein Server Wochen ohne Updates läuft, ist er angreifbar -- auch wenn alles andere perfekt eingerichtet ist. Automatische Updates schließen Lücken, ohne dass du täglich daran denken musst.
# Paket installieren:
sudo apt install unattended-upgrades apt-listchanges
# Automatische Updates aktivieren (beantworte die Frage mit "Yes"):
sudo dpkg-reconfigure -plow unattended-upgrades
# Prüfen ob die Konfiguration aktiv ist:
cat /etc/apt/apt.conf.d/20auto-upgrades
# Erwartete Ausgabe:
# APT::Periodic::Update-Package-Lists "1";
# APT::Periodic::Unattended-Upgrade "1";
# Trockenlauf testen (installiert nichts, zeigt nur was passieren würde):
sudo unattended-upgrades --dry-run --debug
# Log der letzten automatischen Updates prüfen:
sudo cat /var/log/unattended-upgrades/unattended-upgrades.log Unattended-Upgrades installiert standardmäßig nur Sicherheitsupdates aus dem security-Repository -- keine neuen Versionen oder Feature-Updates. Das ist genau richtig: Sicherheitslücken werden automatisch geschlossen, aber keine unkontrollierten Änderungen am System vorgenommen.
Aus Modul 08 weißt du, wie Benutzer und Gruppen funktionieren. Hier geht es darum, sudo-Rechte so knapp wie möglich zu vergeben. Jeder unnötige sudo-Zugang ist eine offene Tür.
Prüfe zuerst, wer überhaupt sudo-Rechte hat:
# Alle Mitglieder der sudo-Gruppe anzeigen:
getent group sudo
# Prüfen welche Rechte ein bestimmter Benutzer hat:
sudo -l -U testuser
# Benutzer aus der sudo-Gruppe entfernen:
sudo deluser testuser sudo
# Letzte Logins aller Benutzer anzeigen:
lastlog
# Benutzer die sich noch nie eingeloggt haben erkennt man an "Never logged in"
# Nur Benutzer anzeigen, die sich schon eingeloggt haben:
lastlog | grep -v "Never logged in" Benutzer die einen Dienst ausführen (z.B. einen Webserver) brauchen meist kein sudo. Lege für jeden Dienst einen eigenen Benutzer ohne Login-Shell an:
# Systembenutzer ohne Login-Shell anlegen (für Dienste):
sudo adduser --system --no-create-home --shell /usr/sbin/nologin webuser
# Prüfen dass der Benutzer sich nicht anmelden kann:
grep webuser /etc/passwd
# Die Shell /usr/sbin/nologin verhindert interaktiven Login Bearbeite /etc/sudoers immer mit sudo visudo -- nie direkt mit einem Editor. visudo prüft die Syntax vor dem Speichern. Ein Syntaxfehler in /etc/sudoers kann dazu führen, dass du dich selbst aus sudo aussperrst.
Offene Ports sind Einfallstore. Jeder Dienst der lauscht, ist ein potentielles Angriffsziel. Aus Modul 14 kennst du die Firewall -- hier geht es darum, zu prüfen was überhaupt läuft, bevor du Firewall-Regeln setzt. Aus Modul 11 weißt du, wie Berechtigungen funktionieren -- hier prüfen wir die kritischsten Dateien.
Welche Ports sind offen und welcher Prozess hört dort?
# Alle lauschenden TCP-Ports mit Prozessnamen anzeigen:
sudo ss -tlnp
# -t = TCP -l = lauschend -n = Nummern statt Namen -p = Prozessname
# Auch UDP-Ports anzeigen:
sudo ss -ulnp
# Alternative mit netstat (falls installiert):
sudo netstat -tlnp Sensible Dateien auf falsche Berechtigungen prüfen -- die Zahlen kennst du aus Modul 11:
# /etc/shadow: nur root und Gruppe shadow dürfen lesen:
ls -la /etc/shadow
# Erwartet: -rw-r----- root shadow ...
# SSH-Schlüssel des Benutzers prüfen:
ls -la ~/.ssh/
# id_ed25519 (privat) muss -rw------- sein (600)
# authorized_keys sollte -rw------- sein (600)
# Dateien mit SUID-Bit finden (können Rechte eskalieren):
find /usr/bin /usr/sbin -perm /4000 -ls
# World-writable Dateien in /etc finden (jeder kann schreiben -- Risiko!):
find /etc -perm -002 -type f -ls 2>/dev/null | Datei / Verzeichnis | Soll-Berechtigung | Prüfbefehl |
|---|---|---|
/etc/shadow | 640 (root:shadow) | ls -la /etc/shadow |
~/.ssh/id_ed25519 | 600 | ls -la ~/.ssh/ |
~/.ssh/authorized_keys | 600 | ls -la ~/.ssh/ |
/etc/ssh/sshd_config | 600 (root only) | ls -la /etc/ssh/sshd_config |
/etc/sudoers | 440 | ls -la /etc/sudoers |
Wenn ein Angreifer es trotzdem auf den Server geschafft hat, hinterlässt er Spuren. Rootkit-Scanner suchen nach bekannten Angriffswerkzeugen und verdächtigen Veränderungen am System.
# rkhunter installieren:
sudo apt install rkhunter
# Datenbank aktualisieren (Referenzwerte für sauberes System speichern):
sudo rkhunter --update
sudo rkhunter --propupd
# System scannen:
sudo rkhunter --check --sk
# --sk = keine Bestätigung bei jedem Schritt nötig
# Nur Warnungen anzeigen:
sudo grep Warning /var/log/rkhunter.log # chkrootkit installieren:
sudo apt install chkrootkit
# System prüfen:
sudo chkrootkit
# Nur Treffer anzeigen:
sudo chkrootkit 2>/dev/null | grep INFECTED Für dauerhafte Überwachung: auditd protokolliert Systemereignisse -- wer hat welche Datei wann geöffnet, welche Befehle wurden als root ausgeführt.
# auditd installieren und aktivieren:
sudo apt install auditd
sudo systemctl enable --now auditd
# Letzte Audit-Ereignisse anzeigen:
sudo ausearch -ts today
# Alle fehlgeschlagenen Logins anzeigen:
sudo ausearch -m USER_LOGIN -sv no
# Audit-Log direkt lesen:
sudo tail -f /var/log/audit/audit.log Führe rkhunter --propupd direkt nach einer Neuinstallation aus, bevor der Server öffentlich erreichbar ist. Das speichert die Referenzwerte eines sauberen Systems. Spätere Scans vergleichen dann mit diesem Ausgangszustand -- so erkennst du Veränderungen zuverlässig.
Diese Checkliste fasst alle Maßnahmen zusammen. Hake sie nach und nach ab:
| Maßnahme | Modul | Priorität |
|---|---|---|
SSH: PermitRootLogin no | 02 + 24 | Kritisch |
SSH: PasswordAuthentication no (nur Keys) | 02 + 24 | Kritisch |
SSH: AllowUsers -- nur bekannte Nutzer | 24 | Hoch |
| Fail2ban aktiv und konfiguriert | 24 | Kritisch |
| Firewall aktiv (ufw), nur nötige Ports offen | 14 | Kritisch |
| Automatische Sicherheitsupdates aktiviert | 24 | Hoch |
| sudo: nur notwendige Benutzer haben Zugang | 08 + 24 | Hoch |
| Berechtigungen: /etc/shadow, SSH-Keys korrekt | 11 + 24 | Hoch |
| Offene Ports geprüft und dokumentiert | 24 | Mittel |
| rkhunter einmalig nach Neuinstallation ausgeführt | 24 | Mittel |
| auditd aktiv für kritische Systeme | 24 | Mittel |
| Backups: automatisiert und getestet | 23 | Hoch |
Fail2ban-Logs zeigen dir, wie viele Angriffe abgeblockt wurden und von wo. Wenn du verstehen willst, was passiert, kopiere ein paar Log-Zeilen und frag: „Hier sind meine fail2ban-Logs. Erkläre mir auf Deutsch, was diese Einträge bedeuten und ob die Konfiguration sinnvoll ist."
Du sicherst den SSH-Zugang deines Servers mit den wichtigsten Einstellungen ab. Das ist die wirkungsvollste Einzelmaßnahme zur Absicherung.
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication|maxauthtries". Notiere die aktuellen Werte.sudo nano /etc/ssh/sshd_config. Setze PermitRootLogin no, MaxAuthTries 3 und LoginGraceTime 30.sudo sshd -t. Kein Fehler? Dann Dienst neu laden: sudo systemctl reload sshd.Lösungshinweise anzeigen
Nach dem Reload sollte sudo sshd -T | grep permitrootlogin den Wert no ausgeben. Eine neue SSH-Verbindung als normaler Benutzer muss funktionieren. Ein Versuch mit ssh root@server sollte mit "Permission denied" fehlschlagen.
Falls sich niemand mehr einloggen kann: Du hast noch das zweite Terminal offen -- bearbeite die Konfiguration dort und lade den Dienst neu.
Du installierst fail2ban und konfigurierst den SSH-Schutz. Danach werden Brute-Force-Angriffe automatisch blockiert.
sudo apt install fail2ban. Aktiviere den Dienst: sudo systemctl enable --now fail2ban.sudo nano /etc/fail2ban/jail.local. Füge die SSH-Jail mit maxretry = 3 und bantime = 3600 ein. Trage dein eigenes Netz in ignoreip ein.sudo systemctl restart fail2ban. Prüfe den Status: sudo fail2ban-client status sshd.sudo tail -20 /var/log/fail2ban.log. Du solltest Einträge mit "INFO" und "NOTICE" sehen.sudo fail2ban-client status sshd. Auf einem öffentlichen Server siehst du oft schon nach kurzer Zeit gebannte Adressen.Lösungshinweise anzeigen
sudo fail2ban-client status sshd zeigt "Currently banned" (aktuell gesperrte IPs) und "Total banned" (gesamt gesperrte IPs seit Start). Auch 0 ist korrekt -- das bedeutet noch hat niemand versucht sich einzuloggen.
Falls der Dienst nicht startet: Prüfe die Konfiguration auf Syntaxfehler mit sudo fail2ban-client -t.
Du machst eine Bestandsaufnahme: Welche Ports sind offen? Sind sensible Dateien korrekt gesichert?
sudo ss -tlnp. Notiere jeden Port und den zugehörigen Prozess. Kennst du alle Prozesse?/etc/shadow: ls -la /etc/shadow. Nur root und die Gruppe shadow dürfen lesen -- Rechte sollten 640 sein.ls -la ~/.ssh/. Der private Schlüssel muss die Rechte 600 haben. Ist das der Fall?find /usr/bin /usr/sbin -perm /4000 -ls. Erkennst du alle Programme in der Liste?getent group sudo. Sind das wirklich alle Personen, die Administratorrechte brauchen?Lösungshinweise anzeigen
Typische legitime SUID-Programme: sudo, su, passwd, ping, newgrp. Wenn unbekannte Programme auftauchen, ist das ein Warnsignal.
Falls SSH-Schlüssel falsche Rechte haben, korrigiere sie: chmod 600 ~/.ssh/id_ed25519 und chmod 700 ~/.ssh/.
Du aktivierst automatische Sicherheitsupdates und führst einen ersten Rootkit-Scan durch -- eine gute Baseline für einen sauberen Server.
sudo apt install unattended-upgrades, dann sudo dpkg-reconfigure -plow unattended-upgrades -- beantworte die Frage mit "Yes".cat /etc/apt/apt.conf.d/20auto-upgrades. Siehst du beide Zeilen mit dem Wert "1"?sudo apt install rkhunter. Aktualisiere die Datenbank: sudo rkhunter --update && sudo rkhunter --propupd.sudo rkhunter --check --sk. Lies die Zusammenfassung am Ende -- wie viele Warnungen gibt es?sudo grep Warning /var/log/rkhunter.log. Auf einem frischen Debian/Ubuntu-System sind die meisten Warnungen false positives -- bekannte harmlose Abweichungen.Lösungshinweise anzeigen
rkhunter gibt auf Debian/Ubuntu typischerweise einige Warnungen aus, die bekannte harmlose Abweichungen sind -- z.B. Skripte die durch das System verändert wurden. Echte Bedrohungen erkennst du an unbekannten Dateien in Systempfaden oder versteckten Prozessen.
Die Datei /etc/apt/apt.conf.d/20auto-upgrades muss beide Zeilen mit "1" enthalten. Falls nicht, öffne die Datei manuell und füge sie hinzu.
Jeder Server hat andere Anforderungen. Lass dir einen individuellen Plan erstellen: „Mein Debian 12 Server läuft mit SSH, Nginx und MariaDB und ist direkt im Internet erreichbar. Ich habe Modul 24 des Linux-Kompendiums durchgearbeitet. Welche weiteren Härtungsmaßnahmen empfiehlst du mir, in welcher Reihenfolge, und warum?"
Systemrettung & Wiederherstellung
Wenn der Server streikt, nicht mehr bootet oder der Bootloader weg ist -- genau dann brauchst du die Werkzeuge aus diesem Modul. Systemrettung ist wie Pannenhilfe: ruhig bleiben, systematisch vorgehen, das richtige Werkzeug wählen.
Systemrettung findet an zwei verschiedenen Orten statt: Manchmal kommst du noch per SSH rein und kannst Diagnose-Befehle ausführen. Manchmal ist das System komplett weg -- dann musst du direkt vor dem Server sitzen oder eine Rescue-Konsole nutzen. Ab jetzt markieren wir in diesem Modul immer klar, wo du dich befindest:
Befehle, die du per SSH auf dem noch erreichbaren Server eingibst -- System läuft noch teilweise.
Befehle an der Server-Konsole -- bei Live-System, Recovery-Mode oder GRUB. Kein SSH möglich.
Dein Server antwortet nicht mehr. Wie gehst du vor? Genau wie ein Kfz-Meister beim Pannenauto: zuerst von außen prüfen, bevor du die Motorhaube öffnest.
Falls SSH noch antwortet, prüfe als erstes diese Punkte:
# Ist der Server im Netz erreichbar?
ping -c 4 192.168.1.50
# SSH-Verbindungstest mit mehr Ausgabe:
ssh -v admin@192.168.1.50
# -v = verbose: zeigt genau, wo die Verbindung hängt Wenn du per SSH draufkommst: prüfe Systemlast und freien Speicher:
# Festplattenplatz -- läuft das System voll?
df -h
# RAM-Auslastung prüfen:
free -h
# Wer frisst die CPU? (q zum Beenden)
top
# Letzte System-Ereignisse anzeigen:
journalctl -n 50 --no-pager | Symptom | Wahrscheinliche Ursache | Erster Schritt |
|---|---|---|
| SSH: "Connection refused" | SSH-Dienst gestoppt oder Port geändert | Konsolen-Zugriff nötig |
| SSH: Timeout (hängt ewig) | Netzwerk-Problem oder Firewall | Ping-Test, Router prüfen |
| SSH: "Connection reset" | System gestürzt oder rebootet | Kurz warten, erneut versuchen |
| Ping: kein Response | Server aus, Netz down oder blockiert | Physischer Zugriff nötig |
| System langsam, kein Login | CPU/RAM/Disk überlastet | top, df -h |
Die wichtigsten Diagnose-Werkzeuge sind die Boot-Logs. Sie erzählen dir, was beim letzten Start passiert ist -- wie ein Fahrtenschreiber im LKW. Du brauchst dafür Zugang zum laufenden System (per SSH oder direkt an der Konsole).
# Boot-Log des letzten Starts anzeigen (-x = erklärender Text, b = aktueller Boot):
journalctl -xb
# Nur Fehler und Warnungen anzeigen:
journalctl -b -p err
# Log eines bestimmten Dienstes anzeigen:
journalctl -u nginx.service --no-pager
# Log des vorletzten Boots (nützlich nach Absturz):
journalctl -b -1
# Live-Ausgabe verfolgen (wie "tail -f" für den Systemlog):
journalctl -f # Alle Kernel-Meldungen anzeigen (besonders Hardware-Fehler):
dmesg
# Nur Fehler und kritische Meldungen:
dmesg --level=err,crit
# Live-Kernel-Meldungen verfolgen:
dmesg -w
# Nach einem bestimmten Begriff suchen:
dmesg | grep -i error Filtere nach diesen Stichwörtern: error, failed, panic, killed, oom (Out of Memory). Ein typischer Hinweis auf einen kaputten Dienst sieht so aus: "nginx.service: Start request repeated too quickly" -- der Dienst startet und stürzt sofort ab.
Boot-Logs können lang und kryptisch sein. Kopiere die relevanten Zeilen und frag: „Mein Debian-Server zeigt beim Booten diese journalctl-Ausgabe: [Zeilen einfügen]. Was bedeuten die Fehlermeldungen und wie behebe ich das Problem Schritt für Schritt?"
Wenn das System noch teilweise bootet, aber du dich nicht einloggen kannst, hilft der Recovery-Modus. Du brauchst dafür physischen Zugang zum Server oder eine Remote-Konsole (z.B. IPMI, KVM oder die Konsole deines Cloud-Anbieters).
Beim Starten des Servers erscheint das GRUB-Menü. Falls es nicht erscheint, halte Shift (BIOS) oder drücke Esc (UEFI) gedrückt.
# 1. Im GRUB-Menü: "Advanced options for Debian GNU/Linux" wählen
# 2. Dann: "... (recovery mode)" auswählen
# 3. Du landest im Recovery-Menü mit diesen Optionen:
# clean -- Speicherplatz freigeben
# dpkg -- kaputte Pakete reparieren
# fsck -- Dateisystem prüfen
# grub -- GRUB-Bootloader reparieren
# network -- Netzwerk aktivieren
# root -- Root-Shell öffnen Mit der Option root öffnest du eine Root-Shell. Dort kannst du z.B. das Root-Passwort zurücksetzen oder einen kaputten Dienst deaktivieren.
Wenn kein Recovery-Modus vorhanden ist (z.B. auf minimalen Installationen), kannst du im GRUB-Menü einen Eintrag direkt bearbeiten:
# 1. Im GRUB-Menü: Kernel-Eintrag markieren, dann 'e' drücken
# 2. In der Zeile, die mit "linux /boot/vmlinuz..." beginnt:
# Am Ende der Zeile 'quiet splash' entfernen und ersetzen durch:
init=/bin/bash
# 3. Booten mit Ctrl+X
# 4. Du landest in einer minimalen Root-Shell (read-only)
# Dateisystem read-write mounten:
mount -o remount,rw /
# Passwort zurücksetzen:
passwd root
# Synchronisieren und Neustart erzwingen:
sync
reboot -f Diese Methode zeigt: Wer physischen Zugriff auf einen Server hat, kann das Root-Passwort zurücksetzen. Deshalb müssen Server immer in abgeschlossenen Serverräumen stehen. Bei Cloud-Servern ist diese Methode nur über die Konsole des Anbieters möglich.
GRUB ist das erste, was beim Serverstart läuft -- bevor der Linux-Kernel geladen wird. Wenn GRUB kaputt ist, bleibt der Bildschirm schwarz oder du siehst einen grub rescue>-Prompt. In diesem Fall brauchst du ein Live-System (USB-Stick oder ISO).
Boote zuerst von einem Live-System, führe dann die chroot-Prozedur durch (siehe Abschnitt "Live-System mounten und chroot"), und repariere dann GRUB von dort aus:
# (Du bist bereits im chroot -- siehe Abschnitt unten)
# GRUB auf BIOS/MBR-System installieren:
grub-install /dev/sda
# Wichtig: /dev/sda ist die GESAMTE Festplatte, keine Partition!
# GRUB auf UEFI-System installieren:
grub-install --target=x86_64-efi --efi-directory=/boot/efi
# GRUB-Konfiguration neu generieren (findet alle Kernel):
update-grub
# Gut: "Found linux image: /boot/vmlinuz-6.1.0-..."
# Schlecht: keine Ausgabe oder nur Fehlermeldungen Nach update-grub und exit aus dem chroot: alles aushängen und neu starten.
| GRUB-Symptom | Bedeutung | Lösung |
|---|---|---|
| Schwarzer Bildschirm nach POST | GRUB-MBR/GPT beschädigt | grub-install vom Live-System |
grub rescue> Prompt | GRUB-Module fehlen oder Pfad falsch | chroot + grub-install |
error: file not found | Kernel-Datei fehlt oder Pfad falsch | update-grub im chroot |
| Altes System wird gebootet | update-grub nach Kernel-Update vergessen | sudo update-grub |
Ein Dateisystem kann nach einem plötzlichen Stromausfall oder Systemabsturz inkonsistent werden -- wie ein Buch, aus dem jemand mitten im Schreiben herausgerissen wurde. fsck prüft das Dateisystem und repariert es, wenn nötig.
fsck auf einer eingehängten (aktiven) Partition kann das Dateisystem endgültig zerstören. Führe fsck immer im abgehängten Zustand aus -- also vom Live-System oder aus dem Recovery-Modus heraus.
# Zuerst herausfinden, welche Partitionen es gibt:
lsblk
fdisk -l
# Typisch: /dev/sda1 = /boot, /dev/sda2 = / (Root)
# Probelauf -- nur prüfen, nichts reparieren:
sudo fsck -n /dev/sda2
# Prüfen und automatisch reparieren (alle Fragen mit "yes" beantworten):
sudo fsck -y /dev/sda2
# Tiefenprüfung mit e2fsck (für ext4-Dateisysteme):
sudo e2fsck -f /dev/sda2 Nach der Reparatur neu starten. Das System sollte jetzt wieder normal booten.
Wenn gar nichts mehr geht, bootest du von einem Live-System (USB-Stick). Von dort aus steigst du per chroot in dein kaputtes System ein -- wie ein Pannendienst, der von außen in die Motorsteuerung eingreift. Diese Technik ist universell: GRUB reparieren, Passwort zurücksetzen, kaputte Pakete beheben -- alles geht über chroot.
Stecke den USB-Stick ein, boote vom Live-Medium (Boot-Reihenfolge im BIOS/UEFI anpassen) und öffne ein Terminal.
lsblk
# Zeigt alle Festplatten und Partitionen
# Typisch: /dev/sda1 = Boot, /dev/sda2 = Root (größere Partition)
fdisk -l
# Detaillierter: zeigt Partitionstypen (Linux, EFI, Swap) # Root-Partition einbinden:
sudo mount /dev/sda2 /mnt
# Boot-Partition einbinden (wenn vorhanden):
sudo mount /dev/sda1 /mnt/boot
# EFI-Partition einbinden (nur bei UEFI-Systemen):
sudo mount /dev/sda1 /mnt/boot/efi
# Pseudo-Dateisysteme einbinden (nötig für GRUB, apt, etc.):
sudo mount --bind /dev /mnt/dev
sudo mount --bind /dev/pts /mnt/dev/pts
sudo mount -t proc proc /mnt/proc
sudo mount -t sysfs sys /mnt/sys
# DNS für apt-Zugriff ermöglichen:
sudo cp /etc/resolv.conf /mnt/etc/resolv.conf
# Ins kaputte System einsteigen:
sudo chroot /mnt /bin/bash
# Ab jetzt bist du "im" kaputten System # chroot verlassen:
exit
# Alles rekursiv aushängen:
sudo umount -R /mnt
# Neu starten:
sudo reboot Lade das Debian-Live-ISO von cdimage.debian.org herunter und schreibe es auf einen USB-Stick. Unter Windows empfiehlt sich das Tool Rufus, unter Linux nutze dd. Den USB-Stick immer griffbereit halten -- im Notfall sucht man ihn sonst vergeblich.
Die meisten Boot-Probleme fallen in wenige Kategorien. Diese Tabelle ist deine erste Anlaufstelle, wenn das System nach einem Eingriff nicht mehr bootet.
| Problem | Symptom | Ursache | Lösung |
|---|---|---|---|
| fstab-Fehler | "Give root password for maintenance" | Falsche UUID oder Partition in /etc/fstab | Recovery-Shell, fstab korrigieren |
| Volle Festplatte | System hängt, Dienste starten nicht | Partition zu 100 % voll | df -h, Logs löschen |
| Defekter Dienst | Boot stoppt, Timeout-Meldungen | Dienst schlägt beim Start fehl | systemctl disable dienst, neu starten |
| Kernel-Panik | "Kernel panic -- not syncing" | Kernel-Bug, Hardware, defekte initramfs | Älteren Kernel aus GRUB wählen |
| Fehlende Abhängigkeit | "A dependency job failed" | Dienst braucht anderen Dienst | journalctl -xb, Fehler finden |
fstab-Fehler korrigieren
Der häufigste Boot-Stopper: eine falsche /etc/fstab. Passiert gern nach einem Festplatten-Tausch oder wenn man UUIDs durcheinanderbringt (Modul 10).
# Aktuelle Block-Geräte mit UUIDs anzeigen:
blkid
# fstab ansehen (lies die UUIDs):
cat /etc/fstab
# fstab bearbeiten:
nano /etc/fstab
# UUID anpassen falls nötig, dann Ctrl+X, Y, Enter
# fstab testen ohne Neustart:
mount -a
# Keine Ausgabe = alles OK. Fehlermeldung = noch ein Fehler in fstab Volle Festplatte -- Notfall-Aufräumen
# Wo ist der Platz weg?
df -h
du -sh /var/log/* | sort -rh | head -20
# Alte Log-Dateien bereinigen:
sudo journalctl --vacuum-size=100M
# APT-Cache leeren:
sudo apt clean
# Große Dateien finden (Verzeichnis /proc ausschließen):
sudo find / -type f -size +100M -not -path '/proc/*' 2>/dev/null Manchmal ist die schnellste Lösung das Backup. Wenn du mit Borg Backup aus Modul 23 arbeitest, kannst du einzelne Dateien oder den ganzen Server gezielt wiederherstellen.
# Liste aller verfügbaren Backups anzeigen:
borg list /backup/borg-repo
# Einzelne Datei wiederherstellen (Beispiel: nginx-Konfiguration):
borg extract /backup/borg-repo::server-2025-11-01 etc/nginx/nginx.conf
# Ganzes Verzeichnis wiederherstellen:
borg extract /backup/borg-repo::server-2025-11-01 etc/nginx/
# Komplette Wiederherstellung in ein Verzeichnis:
cd /tmp/restore
borg extract /backup/borg-repo::server-2025-11-01 In Modul 23 hast du gelernt, wie du mit Borg automatische Backups einrichtest. Teste dein Backup regelmäßig -- am besten monatlich -- damit du im Ernstfall sicher bist, dass es funktioniert. Ein Backup, das noch nie getestet wurde, ist kein echtes Backup.
Jeder Server ist etwas anders. Lass dir eine maßgeschneiderte Checkliste erstellen: „Mein Debian-Server läuft mit UEFI, Borg-Backups und nginx. Erstelle mir eine Schritt-für-Schritt-Notfall-Checkliste für den Fall, dass das System nach einem Update nicht mehr bootet." Diese Checkliste druckst du aus und legst sie neben den Server.
Du lernst, Boot-Logs systematisch auszuwerten. Das ist die Grundlage jeder Diagnose -- immer zuerst in die Logs schauen, bevor du etwas änderst.
journalctl -xb | head -100. Scrolle durch die Ausgabe und suche nach Zeilen mit "failed" oder "error".journalctl -b -p err. Was siehst du?dmesg --level=err,crit. Gibt es Hardware-Fehler oder Speicherprobleme?systemctl --failed. Diese Ansicht zeigt dir sofort, welche Dienste nicht gestartet sind.df -h und free -h. Halte die Ausgaben fest -- das ist dein Basis-Gesundheitscheck.Lösungshinweise anzeigen
journalctl -b -p err zeigt nur Meldungen mit Priorität "error" oder höher. Auf einem gesunden System sind das meist nur Warnungen über fehlende optionale Hardware. systemctl --failed zeigt im normalen Betrieb "0 loaded units listed" -- das ist der Idealzustand.
Wenn du einen fehlgeschlagenen Dienst siehst, schau mit journalctl -u dienstname.service nach den Details.
Du übst die vollständige chroot-Prozedur in einer sicheren VM-Umgebung. Das ist die wichtigste Rettungstechnik -- sie hilft bei GRUB-Problemen, Paket-Schäden und vergessenen Passwörtern.
lsblk und fdisk -l. Notiere dir, welche Partition die Root-Partition des installierten Systems ist (meist die größte).sudo mount /dev/sda2 /mnt (passe sda2 an deine Situation an). Dann die Pseudo-Dateisysteme: sudo mount --bind /dev /mnt/dev, sudo mount -t proc proc /mnt/proc, sudo mount -t sysfs sys /mnt/sys.sudo chroot /mnt /bin/bash. Prüfe, dass du wirklich im installierten System bist: cat /etc/hostname und cat /etc/debian_version.update-grub. Du solltest sehen, dass GRUB die Kernel-Dateien findet. Dann: grub-install /dev/sda.exit und hänge alles aus: sudo umount -R /mnt. Starte die VM neu -- sie sollte normal booten.Lösungshinweise anzeigen
Im chroot zeigt cat /etc/hostname den Hostnamen des installierten Systems -- nicht den des Live-Systems. Das bestätigt, dass du im richtigen System bist. update-grub gibt aus "Found linux image: /boot/vmlinuz-..." -- das ist die Bestätigung, dass GRUB die Kernel-Datei gefunden hat.
umount -R /mnt hängt rekursiv alle Unterverzeichnisse aus. Falls umount meldet "target is busy", hat noch ein Prozess die Dateien geöffnet -- erst exit tippen, dann erneut versuchen.
Ein falscher fstab-Eintrag ist einer der häufigsten Gründe, warum ein Server nach einem Umbau nicht mehr bootet. Du simulierst diesen Fehler in einer VM und behebst ihn über den Recovery-Modus.
sudo cp /etc/fstab /etc/fstab.bak. Dann füge eine ungültige Zeile ein: echo "/dev/sdz1 /mnt/fake ext4 defaults 0 0" | sudo tee -a /etc/fstabsudo mount -a. Du siehst eine Fehlermeldung -- das Gerät existiert nicht. Starte die VM neu: sudo reboot.init=/bin/bash starten.)mount -o remount,rw /. Entferne dann die ungültige Zeile: nano /etc/fstab -- lösche die Zeile mit /dev/sdz1, speichere mit Ctrl+X.mount -a. Keine Fehlermeldung = Erfolg. Starte neu: reboot -f. Das System sollte jetzt normal booten.Lösungshinweise anzeigen
Die Fehlermeldung beim Boot lautet typisch: "Dependency failed for /mnt/fake" oder "failed to mount /mnt/fake". Das System bleibt dann in einem eingeschränkten Modus mit der Aufforderung, das Root-Passwort einzugeben.
In der Recovery-Shell ist das Dateisystem zunächst read-only -- deshalb zuerst mount -o remount,rw /. Nach dem Beheben des fstab-Fehlers und mount -a ohne Fehler-Ausgabe ist das System wieder bootfähig.
Herzlichen Glückwunsch. Du hast das gesamte Linux Server Administration Kompendium abgeschlossen -- 25 Module, 76 Unterrichtseinheiten, von den Grundlagen der Netzwerktechnik bis zur Systemrettung.
Du weißt jetzt, wie du einen Server per SSH fernverwaltest, Dienste mit systemd steuerst, Backups einrichtest, eine Firewall konfigurierst, Docker-Container betreibst und -- genau wie in diesem Modul -- ein kaputtes System wieder zum Laufen bringst. Das ist kein Einsteigerwissen mehr.
Praxis ist alles. Richte einen eigenen Server ein -- ob zuhause auf alter Hardware, auf einem Raspberry Pi oder bei einem günstigen Cloud-Anbieter. Bringe Dienste zum Laufen, mache Fehler, lerne aus den Logs. Das Kompendium ist dein Nachschlagewerk -- komm bei Bedarf zurück.