SSD Nodes Learn 8GB RAM — $66/Jahr
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-01

Shlink selbst hosten: URL-Kürzer mit Docker Compose

Betreiben Sie Shlink 5.1 auf einem VPS: Kurzdomain, HTTPS, Postgres, API-Schlüssel, Webclient, QR-Codes und Klickstatistiken mit Docker Compose.

Was Sie einrichten

Ein selbst gehosteter URL-Kürzer ist ein kleiner Server, der einen langen Link in einen kurzen Link umwandelt, den Sie selbst verwalten, und jeden Klick darauf zählt. Shlink ist dafür die passende Wahl: Die Software ist Open Source, wird als Docker-Image veröffentlicht und erledigt die gesamte Aufgabe in einem Container sowie einer Datenbank. In dieser Anleitung richten Sie Shlink auf einem VPS hinter einer echten Kurzdomain ein, mit HTTPS, einem API-Schlüssel, QR-Codes und Klickstatistiken.

Zwei Komponenten sorgen für das Verhalten eines kommerziellen URL-Kürzers. Der API-Server verarbeitet Weiterleitungen und speichert die Daten. Der Webclient ist eine separate statische Anwendung, die in Ihrem Browser mit dieser API kommuniziert. Sie können beide Komponenten betreiben oder nur die API ausführen und sie über die Befehlszeile steuern.

Die hier genannten Versionsnummern waren im Juli 2026 aktuell: Shlink 5.1 und shlink-web-client 4.8.

Richten Sie zuerst eine kurze Domain auf den Server

Die Domain ist das Produkt. s.example.com/abc123 ist der Link, den andere sehen. Wählen Sie daher einen kurzen Namen, bevor Sie etwas installieren. Shlink speichert die Domain mit jeder Kurz-URL. Wenn Sie sie später ändern, funktionieren alle bereits verteilten Links nicht mehr.

Erstellen Sie einen DNS-A-Record für die kurze Domain, der auf die öffentliche IPv4-Adresse Ihres VPS zeigt. Fügen Sie auch einen AAAA-Record hinzu, wenn der Server IPv6 verwendet. Prüfen Sie anschließend die Auflösung, bevor Sie fortfahren.

dig +short s.example.com A

Die Ausgabe muss die Adresse Ihres Servers sein. Ist sie leer, wurde der Record noch nicht propagiert. Jeder spätere Schritt schlägt dann auf schwer nachvollziehbare Weise fehl, weil für einen Namen, der nicht aufgelöst wird, kein TLS-Zertifikat (Transport Layer Security) ausgestellt werden kann.

Die Compose-Datei

Shlink benötigt eine Datenbank. SQLite eignet sich für einen Test. Für alles, was Sie behalten möchten, ist Postgres die richtige Wahl, weil sich Besuchsdatensätze ansammeln und Postgres Indizes sowie parallele Schreibzugriffe besser verarbeitet. Speichern Sie dies in /opt/shlink/compose.yaml.

services:
  shlink:
    image: shlinkio/shlink:stable
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      DEFAULT_DOMAIN: s.example.com
      IS_HTTPS_ENABLED: "true"
      DB_DRIVER: postgres
      DB_HOST: database
      DB_NAME: shlink
      DB_USER: shlink
      DB_PASSWORD: ${DB_PASSWORD}
    depends_on:
      - database

  database:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: shlink
      POSTGRES_USER: shlink
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - shlink_db:/var/lib/postgresql/data

  web-client:
    image: shlinkio/shlink-web-client:stable
    restart: unless-stopped
    ports:
      - "127.0.0.1:8081:8080"

volumes:
  shlink_db:

Beide veröffentlichten Ports werden an 127.0.0.1 gebunden. Daher ist nichts aus dem Internet erreichbar, bis der Reverse-Proxy im nächsten Abschnitt eingerichtet ist. Docker schreibt seine eigenen Weiterleitungsregeln vor der Host-Firewall. Eine einfache Zeile mit 8080:8080 würde die Anwendung daher auch auf einem System zugänglich machen, dessen Firewall scheinbar alle Zugriffe blockiert. Durch die Bindung an die Loopback-Adresse wird dies verhindert. Dasselbe Muster gilt für jede Anwendung, die Sie auf diese Weise ausführen. Weitere Informationen finden Sie im Leitfaden zu Docker Compose auf einem VPS.

Das Datenbankpasswort stammt aus einer .env-Datei neben der Compose-Datei. Es wird daher nicht in der YAML-Datei gespeichert.

sudo mkdir -p /opt/shlink
printf 'DB_PASSWORD=%s\n' "$(openssl rand -base64 24)" | sudo tee /opt/shlink/.env
sudo chmod 600 /opt/shlink/.env

Starten Sie die Anwendung und überwachen Sie, wie die API gestartet wird.

cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlink

Beim ersten Start werden die Datenbankmigrationen ausgeführt. Daher dauert dieser Start länger als die folgenden. Wenn der Start abgeschlossen ist, prüfen Sie, ob der Dienst lokal antwortet.

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health

Ein 200 bedeutet, dass die API aktiv ist und die Datenbankverbindung funktioniert. Ein 500 an dieser Stelle betrifft fast immer die Datenbank: Der DB_PASSWORD in .env stimmt nicht mit dem Wert überein, mit dem Postgres erstellt wurde. Das Postgres-Image liest POSTGRES_PASSWORD nur, wenn es ein leeres Datenverzeichnis initialisiert. Eine spätere Änderung des Passworts hat keine Wirkung, bis Sie das Volume entfernen und die Anwendung erneut starten.

HTTPS davor terminieren

Shlink stellt unverschlüsseltes HTTP auf Port 8080 bereit. TLS gehört in einen Reverse-Proxy. Entscheidend ist, dass der ursprüngliche Hostnamen weitergegeben wird. Shlink ermittelt anhand des Headers Host, zu welcher Domain ein Kurzcode gehört. Wenn ein Proxy diesen Header überschreibt, antwortet Shlink bei vorhandenen Links mit 404. Außerdem werden die Besuchsstatistiken der falschen Domain zugeordnet.

server {
    server_name s.example.com;
    listen 80;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Stellen Sie anschließend das Zertifikat aus. Die vollständige Anleitung einschließlich des Erneuerungs-Timers finden Sie im Certbot-Leitfaden für nginx unter Ubuntu 24.04.

sudo certbot --nginx -d s.example.com

IS_HTTPS_ENABLED: "true" in der Compose-Datei sorgt dafür, dass Shlink in den zurückgegebenen Kurz-URLs https:// ausgibt. Dadurch wird TLS nicht automatisch aktiviert. Wenn Sie false hinter einem HTTPS-Proxy belassen, ist jeder Link, den die API zurückgibt, ein http://-Link, der anschließend weiterleitet. Das verursacht einen zusätzlichen Roundtrip und wirkt im Webclient fehlerhaft.

API-Schlüssel erstellen

Ohne Schlüssel kann keine Verbindung zur API hergestellt werden. Erstellen Sie einen Schlüssel über die CLI im Container.

sudo docker compose exec shlink shlink api-key:generate --name "web client"

Der Befehl gibt den Schlüssel nur einmal aus. Kopieren Sie ihn jetzt, da er als Hash gespeichert wird und nicht erneut angezeigt werden kann. shlink api-key:list zeigt die Namen und an, ob die einzelnen Schlüssel aktiviert sind, niemals den Schlüssel selbst. Widerrufen Sie einen Schlüssel mit shlink api-key:disable und dem Namen.

Jeder REST-Aufruf übermittelt den Schlüssel in einem X-Api-Key-Header.

curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urls

Ein JSON-Objekt mit einem shortUrls-Schlüssel bedeutet, dass der Schlüssel funktioniert. Ein 401 mit INVALID_API_KEY bedeutet, dass der Schlüssel falsch, deaktiviert oder abgelaufen ist.

Die CLI ist der schnellste Weg, Links zu erstellen. Außerdem lässt sie sich gut in Skripten verwenden.

sudo docker compose exec shlink shlink short-url:create https://example.com/a/very/long/path
sudo docker compose exec shlink shlink short-url:create https://example.com/docs --custom-slug docs --tag reference

--custom-slug erstellt einen lesbaren Link anstelle eines generierten Codes. Slugs sind pro Domain eindeutig. Ein zweiter Versuch mit einem bereits verwendeten Slug schlägt daher fehl, anstatt den ersten Link stillschweigend zu überschreiben. --tag kann wiederholt angegeben werden. Mit Tags gruppieren Sie Links, für die Sie später gemeinsame Statistiken benötigen.

Listen Sie zunächst die vorhandenen Links auf. Sehen Sie sich anschließend den Datenverkehr eines Links an.

sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docs

short-url:visits gibt pro Klick eine Zeile mit Datum, Referrer und User-Agent aus. Die Spalten für Land und Stadt bleiben leer, sofern Sie keine GEOLITE_LICENSE_KEY-Umgebungsvariable setzen. Dabei handelt es sich um einen kostenlosen MaxMind-Schlüssel, den Shlink zum Herunterladen der GeoLite2-Datenbank verwendet. Ohne diesen Schlüssel werden Besuche weiterhin erfasst, aber nicht geografisch zugeordnet.

Der Webclient und QR-Codes

Der Webclient ist jetzt unter 127.0.0.1:8081 verfügbar und benötigt einen eigenen Proxy-Eintrag oder einen SSH-Tunnel, wenn Sie ihn nicht veröffentlichen möchten. Beim ersten Aufruf werden eine Server-URL und ein API-Schlüssel abgefragt. Geben Sie https://s.example.com und den von Ihnen erzeugten Schlüssel ein. Der Client speichert beides im Browserspeicher und ruft Ihre API direkt auf. Dadurch werden keine Daten über Dritte übertragen.

QR-Codes benötigen keinerlei Konfiguration. Hängen Sie /qr-code an eine beliebige Kurz-URL an, und die API gibt das Bild zurück.

https://s.example.com/docs/qr-code?size=500&format=svg&margin=20

size ist die Breite in Pixeln und akzeptiert Werte von 50 bis 1000. Der Standardwert ist 300. format ist png oder svg. margin ist der freie Rand um den Code in Pixeln. Das fertige Bild ist so groß wie die angegebene Größe plus zweimal der Randbreite. Fügen Sie errorCorrection=Q hinzu, damit der Code auch dann gescannt werden kann, wenn er klein gedruckt oder teilweise verdeckt ist.

Betrieb aufrechterhalten

Ein URL-Shortener kann unbemerkt ausfallen. Die Links leiten dann nicht mehr weiter, und niemand informiert Sie darüber, weil die Person, die auf den Link geklickt hat, davon ausgeht, dass der Link nicht funktioniert. Richten Sie die Verfügbarkeitsprüfung auf eine echte Kurz-URL und nicht auf die Startseite. Lösen Sie einen Alarm aus, wenn die Antwort keine Weiterleitung ist. Eine selbst gehostete Uptime-Kuma-Instanz eignet sich dafür gut und kann auf einen bestimmten Statuscode prüfen.

Sichern Sie die Datenbank, nicht den Container. Ein Befehl erstellt einen Dump.

sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gz

Mit dieser Datei und Ihrer Compose-Datei können Sie den gesamten Dienst auf einem neuen Server wiederherstellen. Upgrades bestehen aus sudo docker compose pull gefolgt von sudo docker compose up -d. Shlink führt beim Start alle neuen Migrationen aus. Erstellen Sie den Dump vor dem Abruf der neuen Version, da eine Migration nicht zurückgerollt werden kann.

FAQ

Shlink gleicht einen Kurzcode mit der Domain im Header Host ab. Wenn ein Proxy seinen eigenen Namen oder eine interne Adresse sendet, sucht Shlink den Code unter einer Domain, für die keine Links vorhanden sind. Deshalb wird 404 zurückgegeben. Setzen Sie proxy_set_header Host $host; im nginx-Location-Block und laden Sie den Proxy neu. Die Links funktionieren sofort wieder, ohne dass der Container neu gestartet werden muss.

Benötige ich Postgres, oder reicht SQLite aus?

SQLite eignet sich zum Ausprobieren von Shlink und benötigt keinen zweiten Container. Wechseln Sie zu Postgres, bevor Sie wichtige Links veröffentlichen, da die Besuchszeilen mit jedem Klick wachsen und SQLite Schreibvorgänge serialisiert. Ein späterer Wechsel erfordert den Export und Reimport Ihrer Links. Wenn Sie von Anfang an Postgres wählen, vermeiden Sie diese Migration.

Kann ich einen API-Schlüssel wiederherstellen, den ich nicht kopiert habe?

Nein. Shlink speichert einen Hash des Schlüssels. Daher zeigt api-key:list die Namen und den Status, aber niemals den Wert an. Erzeugen Sie mit shlink api-key:generate einen Ersatzschlüssel, fügen Sie ihn in den Webclient ein und deaktivieren Sie anschließend den alten Schlüssel mit shlink api-key:disable, damit er nicht mehr funktioniert.

Warum sind die Länderspalten in meinen Besuchsstatistiken leer?

Für die Geolokalisierung wird die GeoLite2-Datenbank benötigt. Shlink lädt sie nur herunter, wenn Sie einen GEOLITE_LICENSE_KEY angeben. Der Schlüssel ist kostenlos bei MaxMind erhältlich. Fügen Sie ihn zum Umgebungsabschnitt hinzu, erstellen Sie den Container neu, und neue Besuche werden lokalisiert. Besuche, die vorher erfasst wurden, bleiben leer, bis Sie shlink visit:locate ausführen.

Behalten Sie die Domain bei und verschieben Sie die Daten. Sichern Sie die Datenbank mit pg_dump, kopieren Sie den Dump und die Compose-Datei auf den neuen Server, starten Sie den Stack und stellen Sie den Dump in der leeren Datenbank wieder her, bevor echter Netzwerkverkehr eintrifft. Ändern Sie den DNS-Eintrag zuletzt. Die Kurzodes und ihre Besuchshistorie bleiben erhalten, da alle Daten in der Datenbank gespeichert sind.

#shlink#url-shortener#self-hosting#docker#postgres