Shlink selbst hosten: URL-Kürzer mit Docker Compose und MariaDB
Eigener URL-Kürzer mit Shlink 5.1.7 und MariaDB per Docker Compose: .env und compose.yaml, Prüfung über die REST-API, Reverse Proxy mit HTTPS, API-Schlüssel mit Rollen, Backup, Update und typische Fehler.
Geprüft am 16.10.2026 · für Shlink 5.1.7
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Shlink ist ein quelloffener URL-Kürzer unter MIT-Lizenz. Er liefert für lange Adressen kurze Links unter Ihrer eigenen Domain und bringt eine REST-API sowie eine Kommandozeile mit. Anders als bei einem kostenlosen Kürzungsdienst entscheiden Sie selbst, welche Links existieren, wohin sie zeigen und wer die Besuchsstatistik sieht. Schaltet ein fremder Anbieter ab, laufen gedruckte QR-Codes und Links in Signaturen ins Leere. Diese Anleitung baut Shlink 5.1.7 mit MariaDB per Docker Compose auf und behandelt Reverse Proxy, API-Schlüssel, Backup und Update.
Voraussetzungen
- Linux-Server mit Docker Engine und Compose-Plugin.
docker compose versionmuss eine Version ausgeben. - 2 CPU-Kerne und 2 bis 4 GB RAM genügen. Laut Dokumentation begrenzt
MEMORY_LIMITjeden Shlink-Prozess auf 512 MB, die Zahl der Prozesse (WEB_WORKER_NUM) entspricht standardmäßig der Zahl der CPU-Kerne. Setzen Sie den Wert auf Servern mit vielen Kernen bewusst. - Einige GB Speicher für das Datenbank-Volume. Das Wachstum bestimmen die gespeicherten Besuche, nicht die Kurzlinks.
- Eine möglichst kurze Domain oder Subdomain mit A- bzw. AAAA-Eintrag auf den Server.
- Ein Reverse Proxy mit TLS-Zertifikat, denn Shlink terminiert kein TLS.
| Eckdatum | Wert |
|---|---|
| Images | shlinkio/shlink:5.1.7, mariadb:11 |
| Port | 127.0.0.1:8085 auf Container-Port 8080 |
| Volume | shlink_db_data unter /var/lib/mysql |
| Wichtige Variablen | DEFAULT_DOMAIN, IS_HTTPS_ENABLED, DB_DRIVER, INITIAL_API_KEY, TIMEZONE |
Eine grafische Verwaltung bringt Shlink nicht mit, dafür gibt es das separate Projekt shlink-web-client. Die Standortermittlung von Besuchen braucht laut Dokumentation einen GeoLite2-Lizenzschlüssel. SQLite ist nur für Tests vorgesehen, deshalb nutzt diese Anleitung MariaDB.
Schritt 1: .env anlegen
Legen Sie das Verzeichnis /opt/shlink an und darin eine .env. Alle Werte sind Platzhalter:
# Passwort für den Datenbankbenutzer, bitte ersetzen
SHLINK_DB_PASSWORD=BITTE-HIER-EIN-LANGES-ZUFALLSPASSWORT-EINSETZEN
# Erster API-Schluessel, bitte ersetzen
SHLINK_INITIAL_API_KEY=BITTE-HIER-EINEN-ZUFALLSWERT-EINSETZEN
# Die eigene Kurzdomain ohne Schema und ohne Schraegstrich
SHLINK_DEFAULT_DOMAIN=kurz.example.com
Zufallswerte erzeugen Sie auf dem Server:
# Zwei unabhaengige Zufallswerte erzeugen
openssl rand -base64 33
openssl rand -hex 32
Die Datei enthält Datenbankpasswort und Administratorschlüssel. Beschränken Sie den Zugriff und halten Sie sie aus der Versionsverwaltung heraus:
# Nur der Eigentuemer darf lesen und schreiben
chmod 600 .env
# Datei dauerhaft aus der Versionsverwaltung heraushalten
echo ".env" >> .gitignore
Ein Schlüssel in einer Umgebungsvariablen ist für jeden lesbar, der docker inspect ausführen darf, und steht in jeder Sicherung des Verzeichnisses. Behandeln Sie ihn wie ein Passwort.
Verifizieren: ls -l /opt/shlink/.env zeigt -rw-------, und grep BITTE /opt/shlink/.env liefert keine Treffer mehr.
Schritt 2: compose.yaml erstellen
Diese Datei wurde im Test verwendet:
services:
shlink_db:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: ${SHLINK_DB_PASSWORD}
MARIADB_DATABASE: shlink
MARIADB_USER: shlink
MARIADB_PASSWORD: ${SHLINK_DB_PASSWORD}
volumes:
- shlink_db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
shlink:
image: shlinkio/shlink:5.1.7
restart: unless-stopped
depends_on:
shlink_db:
condition: service_healthy
environment:
DEFAULT_DOMAIN: ${SHLINK_DEFAULT_DOMAIN}
IS_HTTPS_ENABLED: "false"
DB_DRIVER: maria
DB_HOST: shlink_db
DB_NAME: shlink
DB_USER: shlink
DB_PASSWORD: ${SHLINK_DB_PASSWORD}
INITIAL_API_KEY: ${SHLINK_INITIAL_API_KEY}
TIMEZONE: Europe/Berlin
ports:
- "127.0.0.1:8085:8080"
volumes:
shlink_db_data:
Die wichtigsten Punkte:
- Feste Versionen:
shlinkio/shlink:5.1.7entspricht laut Dokumentation genau dieser Version,stablewandert mit.mariadb:11liefert Sicherheitsupdates innerhalb der Hauptversion. - Startreihenfolge: Der Healthcheck ruft das mitgelieferte
healthcheck.sh --connect --innodb_initializedauf. Mitcondition: service_healthystartet Shlink erst, wenn MariaDB Verbindungen annimmt. - Erstinitialisierung:
MARIADB_USERundMARIADB_PASSWORDwirken nur bei einem leeren Volume. Spätere Passwortänderungen nehmen Sie in der Datenbank selbst vor. - DB_HOST: shlink_db ist der Dienstname.
localhostwürde im Shlink-Container auf diesen selbst zeigen.DB_DRIVER: mariaist der dokumentierte Wert für MariaDB. - INITIAL_API_KEY legt beim Start einmalig einen Administratorschlüssel an. Laut Dokumentation wird die Variable ignoriert, sobald Schlüssel existieren, und Ändern oder Entfernen wirkt nicht auf den bestehenden Schlüssel.
- TIMEZONE verhindert, dass Zeitstempel in UTC landen, der PHP-Vorgabe im Image.
- Port:
127.0.0.1:8085:8080macht Shlink nur für den Reverse Proxy auf demselben Host erreichbar.
Verifizieren: docker compose config --quiet im Verzeichnis /opt/shlink endet ohne Ausgabe.
Schritt 3: Stack starten
# Arbeitsverzeichnis anlegen und betreten
mkdir -p /opt/shlink
cd /opt/shlink
# Danach compose.yaml und .env in diesem Verzeichnis ablegen
# Images herunterladen und Stack im Hintergrund starten
docker compose up -d
# Status der Dienste ansehen
docker compose ps
Im Test erreichte der Datenbankcontainer zuerst Up (healthy), danach startete Shlink mit Up. Hakt etwas, helfen die Protokolle:
# Protokoll beider Dienste laufend mitlesen
docker compose logs -f
# Nur den Shlink-Dienst ansehen, letzte 50 Zeilen
docker compose logs --tail 50 shlink
Verifizieren: docker compose ps zeigt shlink_db als healthy und shlink als Up, ohne wiederholte Neustarts.
Schritt 4: Funktion über die API prüfen
Der Health-Endpunkt braucht keinen Schlüssel:
# Gesundheitszustand der Instanz abfragen
curl -s http://127.0.0.1:8085/rest/health
Im Test kam diese Antwort, die Version stimmt also mit dem Tag überein:
{"status":"pass","version":"5.1.7","links":{"about":"https://shlink.io","project":"https://github.com/shlinkio/shlink"}}
Legen Sie eine erste Kurz-URL an. Den Schlüssel übergeben Sie im Header X-Api-Key:
# Erste Kurz-URL anlegen
curl -s -X POST http://127.0.0.1:8085/rest/v3/short-urls -H "X-Api-Key: IHR-API-SCHLUESSEL" -H "Content-Type: application/json" -d '{"longUrl":"https://example.org/eine/lange/adresse","tags":["test"]}'
Im Test enthielt die Antwort shortUrl, shortCode, tags, crawlable: false, forwardQuery: true und einen Besuchszähler von 0. Zwei Beobachtungen zählen: Die shortUrl wurde aus DEFAULT_DOMAIN gebildet, nicht aus der lokalen Adresse des Aufrufs. Und Shlink hatte selbst den Titel der Zielseite abgerufen. Diesen ausgehenden Zugriff steuert laut Dokumentation AUTO_RESOLVE_TITLES (Vorgabe true). Setzen Sie ihn auf false, wenn Shlink keine beliebigen Ziele kontaktieren soll.
# Die letzten drei Kurz-URLs auflisten
curl -s "http://127.0.0.1:8085/rest/v3/short-urls?itemsPerPage=3" -H "X-Api-Key: IHR-API-SCHLUESSEL"
Verifizieren: /rest/health meldet "status":"pass" mit Version 5.1.7, und die Liste enthält die eben angelegte Kurz-URL mit Ihrer Kurzdomain.
Schritt 5: Reverse Proxy und HTTPS einrichten
Die Dokumentation beschreibt den Betrieb hinter Nginx, Apache oder Caddy. Der Proxy nimmt HTTPS an und leitet an 127.0.0.1:8085 weiter. Er muss Besucher-IP und angefragte Domain weiterreichen, also Host, X-Real-IP, X-Forwarded-For und X-Forwarded-Proto setzen. Bei mehreren Proxys hintereinander geben Sie über TRUSTED_PROXIES deren Anzahl oder Adressen an.
In der .env bzw. compose.yaml passen Sie zwei Werte an: DEFAULT_DOMAIN enthält genau Ihre Kurzdomain ohne Schema und Schrägstrich. IS_HTTPS_ENABLED steht auf true, sonst gibt Shlink Links mit http:// aus. Für den Test stand er bewusst auf false. Ein Neustart reicht nicht, der Container muss neu erzeugt werden:
# Container mit geaenderten Umgebungsvariablen neu erzeugen
docker compose up -d
# Danach pruefen, welche Werte im Container wirklich ankommen
docker compose exec shlink env | grep -E 'DEFAULT_DOMAIN|IS_HTTPS_ENABLED'
Bestehende Kurzcodes bleiben dabei erhalten, Shlink gibt sie nur unter der neuen Adresse aus. Unbekannte Codes leiten Sie mit DEFAULT_INVALID_SHORT_URL_REDIRECT und DEFAULT_REGULAR_404_REDIRECT auf eine eigene Seite.
Verifizieren: Die env-Ausgabe zeigt Ihre Domain und IS_HTTPS_ENABLED=true, eine neu angelegte Kurz-URL beginnt mit https://, und curl -I auf diese Adresse liefert eine Weiterleitung (Status 3xx) auf das Ziel.
Schritt 6: API-Schlüssel mit Rollen anlegen
Laut Dokumentation verwalten Sie Schlüssel über die Kommandozeile im Container:
# Alle vorhandenen API-Schluessel auflisten
docker compose exec shlink shlink api-key:list
# Neuen benannten Schluessel mit Rolle AUTHORED_SHORT_URLS erzeugen
docker compose exec shlink shlink api-key:generate --name=monitoring --author-only
# Einen Schluessel ueber seinen Namen deaktivieren
docker compose exec shlink shlink api-key:disable monitoring
# Alle verfuegbaren Befehle anzeigen
docker compose exec shlink shlink
Seit Shlink 4.3 werden Schlüssel gehasht gespeichert. Den Klartext sehen Sie nur einmal bei der Erzeugung, legen Sie ihn sofort sicher ab. Ohne Rolle ist ein Schlüssel ein Administratorschlüssel. Einschränken lässt er sich mit AUTHORED_SHORT_URLS (nur selbst erzeugte Links), DOMAIN_SPECIFIC (eine Domain) und NO_ORPHAN_VISITS. Rollen lassen sich nachträglich nicht ändern, dann deaktivieren Sie den Schlüssel und legen einen neuen an. Die Rollen vergeben Sie beim Erzeugen mit --author-only, --domain-only=DOMAIN und --no-orphan-visits, ein Ablaufdatum mit --expiration-date. Im Test sah ein Schlüssel mit --author-only nur seinen eigenen Kurzlink, der Administratorschlüssel beide. Deaktivieren erwartet in Shlink 5.1 nur den Namen, eine Option --by-name gibt es nicht. Ein deaktivierter Schlüssel erhielt danach HTTP 401.
Nutzen Sie den Schlüssel aus INITIAL_API_KEY nur für die Einrichtung und geben Sie jeder Integration einen eigenen, benannten Schlüssel mit enger Rolle. Ein kompromittierter Schlüssel betrifft dann nur diese Integration. Breit zugängliche Kürzer werden gern für Phishing missbraucht.
Verifizieren: Die Schlüsselliste zeigt den neuen Schlüssel mit Namen und Rolle als aktiv, ein Aufruf mit diesem Schlüssel gegen /rest/v3/short-urls liefert nur die erlaubten Links.
Schritt 7: Backup und Update
Alle Kurzlinks, Schlagwörter und Besuche liegen im Volume shlink_db_data, der Shlink-Container hält keine Nutzdaten. Zur Sicherung gehören außerdem compose.yaml und .env. Den Volumenamen bildet Compose aus Verzeichnis und Dateiname, bei /opt/shlink also shlink_shlink_db_data:
# Vorhandene Volumes des Stacks anzeigen
docker volume ls --filter name=shlink
# Details zu einem Volume, unter anderem der Pfad auf dem Host
docker volume inspect shlink_shlink_db_data
Die Befehle für Dump und Wiederherstellung verwenden $SHLINK_DB_PASSWORD auf dem Host. Laden Sie die .env deshalb zuerst in die Shell:
# Variablen aus der .env in die aktuelle Shell laden
cd /opt/shlink
set -a; . ./.env; set +a
# Datenbank in eine komprimierte Datei mit Zeitstempel sichern
docker compose exec -T shlink_db mariadb-dump --single-transaction -u shlink -p"$SHLINK_DB_PASSWORD" shlink | gzip > shlink-$(date +%F).sql.gz
# Ergebnis auf plausible Groesse pruefen
ls -lh shlink-*.sql.gz
--single-transaction liefert bei InnoDB einen konsistenten Stand ohne Tabellensperre. Ein Dump von wenigen hundert Byte deutet fast immer auf ein falsches Passwort. Zur Wiederherstellung halten Sie Shlink an, damit keine Schreibzugriffe dazwischenkommen:
# Den Shlink-Dienst anhalten, damit keine Schreibzugriffe stoeren
docker compose stop shlink
# Dump zurueckspielen
gunzip -c shlink-2026-09-21.sql.gz | docker compose exec -T shlink_db mariadb -u shlink -p"$SHLINK_DB_PASSWORD" shlink
# Dienst wieder starten
docker compose start shlink
# Gegenprobe ueber die API
curl -s "http://127.0.0.1:8085/rest/v3/short-urls?itemsPerPage=3" -H "X-Api-Key: IHR-API-SCHLUESSEL"
Für Updates erscheint zu jeder Version ein neuer Image-Tag, Datenbankanpassungen erledigt das Image beim Start. Die Dokumentation empfiehlt, nicht zu viele Versionen auf einmal zu überspringen, sondern über die jeweils letzte Patch-Version einer Nebenversion zu gehen, und vorher die Datenbank zu sichern:
# 1. Datenbank sichern, siehe vorheriger Abschnitt
# 2. Den Versions-Tag in der compose.yaml auf die neue Version aendern
# 3. Neues Image laden
docker compose pull
# 4. Container mit neuem Image neu erzeugen
docker compose up -d
# 5. Protokoll auf Migrationsmeldungen und Fehler pruefen
docker compose logs --tail 100 shlink
# 6. Laufende Version gegenpruefen
curl -s http://127.0.0.1:8085/rest/health
Im Test lief das Update von 5.1.6 auf 5.1.7 ohne Fehler im Log, /rest/health meldete danach 5.1.7, alle Kurzlinks blieben erhalten. Nach einer Migration kennt die alte Version das Schema nicht mehr. Ein Rollback gelingt verlässlich nur mit altem Tag plus Dump von vor dem Update. Feste Tags sorgen dafür, dass Sie den Zeitpunkt einer Migration bestimmen und nicht ein zufälliger Neustart.
Verifizieren: Die Dump-Datei ist einige KB groß (im Test mit zwei Kurzlinks 3,2 KB komprimiert), /rest/health meldet nach dem Update die neue Version, und die Liste der Kurz-URLs ist vollständig.
Typische Fehler
Kurzlinks zeigen auf die falsche Adresse. Im Test aufgetreten: Die API quittierte das Anlegen mit Erfolg, die shortUrl war aber unter der aufgerufenen Adresse nicht erreichbar, weil Shlink sie aus DEFAULT_DOMAIN bildet. Mit Platzhalter oder falscher Domain entstehen unbrauchbare Links, oft erst nach dem Verteilen bemerkt. Prüfen Sie docker compose exec shlink env | grep DEFAULT_DOMAIN, korrigieren Sie den Wert und führen Sie docker compose up -d aus.
Shlink startet vor der Datenbank und beendet sich. Ohne Healthcheck und condition: service_healthy startet Shlink, sobald der MariaDB-Container existiert, nicht wenn er bereit ist. Im Test löste genau diese Kombination das Problem.
Falsches Datenbankpasswort. Meist wurde das Passwort in der .env geändert, während das Volume aus einem früheren Lauf noch existiert. Die MariaDB-Variablen wirken nur bei der Erstinitialisierung. Prüfen Sie docker compose logs shlink_db und ändern Sie das Passwort in der Datenbank.
Port bereits belegt. ss -tlnp | grep 8085 zeigt den Verursacher. Wählen Sie einen freien Port und ziehen Sie den Reverse Proxy nach.
Links beginnen mit http. IS_HTTPS_ENABLED steht auf false. Auf true setzen und den Container neu erzeugen.
API antwortet mit 401. Laut Dokumentation ist der Schlüssel dann fehlend, ungültig, deaktiviert oder abgelaufen. Prüfen Sie den Headernamen X-Api-Key und den Status in der Schlüsselliste.
Häufige Fragen
Warum legt Shlink beim zweiten Start keinen neuen API-Schlüssel an?
Das ist dokumentiertes Verhalten: INITIAL_API_KEY wird ignoriert, sobald Schlüssel existieren. Weitere Schlüssel erzeugen Sie über die Kommandozeile im Container.
Kann Shlink QR-Codes erzeugen?
Die QR-Code-Funktion gilt laut Dokumentation seit Version 4.5.0 als veraltet. Erzeugen Sie QR-Codes außerhalb von Shlink.
Welche Datenbanken unterstützt Shlink?
Laut Dokumentation MySQL, MariaDB, PostgreSQL, Microsoft SQL Server und SQLite, wobei SQLite nur für Tests gedacht ist.
Wie schalte ich Shlink ab, ohne Daten zu verlieren?
docker compose down entfernt Container und Netz, das Volume bleibt. Achtung: docker compose down -v löscht die Datenbank mit allen Kurzlinks und Statistiken unwiderruflich.
# Variante 1: Container stoppen und entfernen, Daten bleiben erhalten
docker compose down
# Variante 2: ACHTUNG, entfernt zusaetzlich die Volumes
# Damit werden die Datenbank und alle Kurzlinks unwiederbringlich geloescht
docker compose down -v
# Volumes des Stacks auflisten
docker volume ls --filter name=shlink
# Nicht mehr benoetigte Images entfernen
docker image rm shlinkio/shlink:5.1.7 mariadb:11
Verteilte Kurzlinks stehen in E-Mails, auf Drucksachen und fremden Webseiten. Klären Sie vor einer Abschaltung, welche Links im Umlauf sind, und leiten Sie die Domain auf eine Ersatzseite um, statt sie verfallen zu lassen.
Fazit
Shlink ist ein schlanker, gut automatisierbarer URL-Kürzer: zwei Container, ein Volume, Konfiguration komplett über Umgebungsvariablen. Für einen stabilen Betrieb zählen vier Dinge: DEFAULT_DOMAIN und IS_HTTPS_ENABLED richtig setzen, je Integration einen eigenen Schlüssel mit Rolle vergeben, feste Versionen verwenden und vor jedem Update einen Dump ziehen.
Weiterführende Anleitungen und Quellen
- Traefik als Docker Reverse Proxy mit HTTPS einrichten: Betrieb unter eigener Domain mit automatischen Zertifikaten.
- Docker Compose Grundlagen und Stacks: Dienste, Volumes und Netzwerke von Grund auf.
- Docker Compose absichern mit Secrets, Healthchecks und Non-Root: Geheimnisse aus Umgebungsvariablen heraushalten.
- GitHub-Repository shlinkio/shlink
- Release-Übersicht
- Shlink-Dokumentation: Installation per Docker-Image
- Shlink-Dokumentation: Umgebungsvariablen
- Shlink-Dokumentation: Authentifizierung der REST-API
- Shlink-Dokumentation: Rollen für API-Schlüssel
- Shlink-Dokumentation: Instanz aktualisieren
- Shlink-Dokumentation: Betrieb hinter einem Reverse Proxy
- Shlink-Dokumentation: unterstützte Datenbanken


