Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 21.09.2026 · 12 min Lesezeit

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

Silberne Box auf hellem Tisch mit zwei verbundenen Kettengliedern und aufgerolltem Stahlkabel vor Fensterausblick

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 version muss eine Version ausgeben.
  • 2 CPU-Kerne und 2 bis 4 GB RAM genügen. Laut Dokumentation begrenzt MEMORY_LIMIT jeden 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.
EckdatumWert
Imagesshlinkio/shlink:5.1.7, mariadb:11
Port127.0.0.1:8085 auf Container-Port 8080
Volumeshlink_db_data unter /var/lib/mysql
Wichtige VariablenDEFAULT_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.7 entspricht laut Dokumentation genau dieser Version, stable wandert mit. mariadb:11 liefert Sicherheitsupdates innerhalb der Hauptversion.
  • Startreihenfolge: Der Healthcheck ruft das mitgelieferte healthcheck.sh --connect --innodb_initialized auf. Mit condition: service_healthy startet Shlink erst, wenn MariaDB Verbindungen annimmt.
  • Erstinitialisierung: MARIADB_USER und MARIADB_PASSWORD wirken nur bei einem leeren Volume. Spätere Passwortänderungen nehmen Sie in der Datenbank selbst vor.
  • DB_HOST: shlink_db ist der Dienstname. localhost würde im Shlink-Container auf diesen selbst zeigen. DB_DRIVER: maria ist 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:8080 macht 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

Das ist dokumentiertes Verhalten: INITIAL_API_KEY wird ignoriert, sobald Schlüssel existieren. Weitere Schlüssel erzeugen Sie über die Kommandozeile im Container.

Die QR-Code-Funktion gilt laut Dokumentation seit Version 4.5.0 als veraltet. Erzeugen Sie QR-Codes außerhalb von Shlink.

Laut Dokumentation MySQL, MariaDB, PostgreSQL, Microsoft SQL Server und SQLite, wobei SQLite nur für Tests gedacht ist.

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

ShlinkURL-KürzerDocker ComposeMariaDBSelfhostingREST-APIReverse Proxy