Um Links, die bspw. von diesem Blog auf Twitter oder sonst irgendwo auf Social Media landen, zu verkürzen, verwende ich schon ewig Bitly. Genau genommen seit 2009, anfangs noch unter der Domain bit.ly, später mit einer eigenen Domain. Schon länger will ich eigentlich weg von dem Dienst.

Nicht, dass ich unzufrieden wäre, aber nach und nach wurde der kostenlose Account immer mehr eingeschränkt und selbst die eigene Domain hat mein Account wohl nur behalten dürfen, weil diese schon ewig genutzt wurde. Wer sich heute frisch anmeldet, braucht einen bezahlten Zugang, um diese Möglichkeit nutzen zu können. Zusätzlich wurde wohl Werbung „zwischengeschaltet“. Und, bei aller Liebe, für das, was ich hier nutze (nämlich eigentlich nur die pure Weiterleitung), bin ich nicht bereit 10 Euro oder mehr im Monat zu zahlen.
Quasi als „Bonus“ kam dazu, dass ich ein wenig weg von dem ganzen US-Kram will. Nicht komplett, dafür verwende ich Google, Apple, Amazon und einige andere zu häufig. Aber wenn ich der Meinung bin, ich sollte etwas ändern, dann schaue ich mich nach einer Alternative um, die nach Möglichkeit in Europa liegt. Wenn meine Daten dann noch bei mir bleiben können, umso besser. Zum Beispiel habe erst gestern nach fast 14 Jahren mein Dropbox-Abo gekündigt. Vielleicht schreibe ich bei Gelegenheit etwas zum Umzug auf Hetzner Storage Share/Nextcloud.
Wie auch immer, ich hatte schon eine Weile die Idee, die Funktionalität von Bitly einfach selbst zu hosten. Es gibt eine ganze Reihe URL-Shortener, der bekannteste ist wahrscheinlich YOURLS. Ich habe mir ein paar angeschaut, ein wenig gelesen und mich dann für Shlink entschieden. Dieser hat die Möglichkeit, Links von Bitly zu importieren, was Grundvoraussetzung war. Immerhin will ich den Übergang 1:1 sauber abschließen und nicht noch tagelang rumfrickeln.
Was ist Shlink?
Das Schöne ist also, dass der Umzug von Bitly kein Hexenwerk ist. Aber mal kurz zu Shlink an sich:
Shlink ist open‑source (MIT License) URL-Shortener oder auf Deutsch Linkverkürzer oder Kurz-URL-Dienst. Dieser dient dazu, aus URLs wie https://github.com/shlinkio/shlink/releases/tag/v5.0.0 einen kürzeren Link wie https://mk.cx/shlink5 zu erzeugen. Zusätzlich gibt es noch (DSGVO-konforme) Statistiken, wie oft auf einen Link geklickt wurde und woher der Klicker kam. Shlink wird selbstgehostet und bietet daher natürlich die volle Kontrolle über alle Daten. Im Gegensatz zu Anbietern wie Bitly oder TinyURL gibt es keine Limits (außer hardwareseitig irgendwann). Man kann problemlos eine eigene Domain verwenden, sollte sich aber, wie bei eigentlich allem Selbstgehosteten, etwas mit Docker (compose) und DNS auskennen.
Shlink selbst besteht aus einem Server-Container, der die Weiterleitungen, die REST-API und das CLI bereitstellt und mit der Datenbank kommuniziert. Zusätzlich gibt es optional ein separates Web-Interface (shlink-web-client), das die Verwaltung im Browser ermöglicht. Es gibt auch noch ein paar andere Möglichkeiten, auf Shlink zuzugreifen.
Aber kommen wir zum Umzug. In diesem Artikel zeige ich dir Schritt für Schritt, wie ich meinen Bitly‑Account zu Shlink migriert habe, wie ich Shlink über Docker Compose installiert habe, wie ich die Web‑GUI einrichte, wie ich die Links per API importiere und wie die DNS‑Einträge aussehen müssen. Let’s Encrypt lasse ich bewusst weg, da der Artikel sonst zu ausführlich wäre und ich das einfach mal voraussetze.
Vorbereitung: Domains und DNS-Einträge
Du benötigst zwei Subdomains (oder eine Domain und eine Subdomain). Für dieses Setup verwende ich:
- Shortlink‑Domain: Über Subdomain sh.
example.com(z.B. sh.example.com/xyz123) oderexample.com (example.com/xyz123) - GUI‑Domain:
ui.example.com(z.B.ui.example.comfür die Shlink‑Web‑GUI) - Server‑IP:
123.45.67.89
Die Domains müssen auf den Server zeigen, den du für Shlink nutzt. Bei deinem DNS‑Provider (z.B. Cloudflare, Hetzner DNS) legst du folgende Einträge an. Entweder vorab, wenn du ggf. mit ein paar Minuten Downtime leben kannst, oder wenn alles fertig ist:
- A‑Record für
example.com→123.45.67.89 - A‑Record für
ui.example.com→123.45.67.89 - ggf. A‑Record für
sh.example.com→123.45.67.89
Shlink über Docker Compose installieren
Ich setze voraus, dass Docker und Docker Compose auf deinem Server installiert ist. Wie das geht, siehst du auf Docker.com. Ich verwende gerne ein Frontend wie Dockge, du wirst aber schon wissen, was zu tun ist, wenn du direkt auf der Shell arbeitest.
Ich habe bereits eine MariaDB-Instanz auf derselben Maschine laufen. Shlink unterstützt mehrere Datenbank-Engines, von denen du eine vorab installiert haben solltest. Des Weiteren benötigst du einen API-Key. Diesen kannst du entweder erstmal leer lassen und mit docker exec -it shlink shlink api-key:generate erzeugen, wenn der Container läuft. Alternativ geht auch vorab openssl rand -base64 32. Optional benötigst du einen Account und einen Key von Maxmind für die GeoLite2 Datenbank (Um sehen zu können, woher die Besucher deiner Links kommen).
Mein docker-compose.yml sieht wie folgt aus:
services:
shlink:
image: shlinkio/shlink:latest
ports:
- 127.0.0.1:1234:8080
environment:
DEFAULT_DOMAIN: example.com (oder sh.example.com)
IS_HTTPS_ENABLED: "true"
# DB (MariaDB on host)
DB_DRIVER: maria
DB_HOST: host.docker.internal
DB_PORT: "3306"
DB_NAME: shlink
DB_USER: user
DB_PASSWORD: password
INITIAL_API_KEY: LANGERKEY
TIMEZONE: Europe/Berlin
GEOLITE_LICENSE_KEY: KEY
TRUSTED_PROXIES: 127.0.0.1,172.16.0.0/12
extra_hosts:
- host.docker.internal:host-gateway
restart: unless-stopped
shlink-web:
image: shlinkio/shlink-web-client:latest
ports:
- 1235:80
environment:
SHLINK_SERVER_URL: https://example.com (oder https://sh.example.com)
volumes:
- /etc/nginx/ui.conf:/etc/nginx/conf.d/default.conf:ro
restart: unless-stopped
networks: {}
Das installiert das CLI und das Webinterface und verbindet sich mit der Datenbank. Weitere Umgebungsvariablen findest du hier. Du solltest zwei Container sehen: shlink und shlink-web. Shlink läuft auf Port 1234 und die Website auf Port 1235. Der Rest sollte eigentlich selbsterklärend sein.
Die Datei /etc/nginx/ui.conf war nötig, weil die GUI andernfalls bei mir Probleme gemacht hat. Das muss nicht zwingend bei dir auch so sein.
# /etc/nginx/ui.conf
server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } }
Reverse-Proxy über Nginx
Bevor wir das Webinterface nutzen können, müssen wir für Nginx zwei Virtual Hosts anlegen.
# HTTP → HTTPS example.com (oder sh.example.com)
server {
listen 123.45.67.89:80;
server_name example.com;
return 301 https://example.com$request_uri;
}
# HTTPS example.com → Backend (oder sh.example.com)
server {
listen 123.45.67.89:443 ssl;
http2 on;
server_name example.com;
access_log /logs/example.com/access_log;
error_log /logs/example.com/error_log;
ssl_certificate /etc/letsencrypt/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/example.com/key.pem;
ssl_trusted_certificate /etc/letsencrypt/example.com/ca.pem;
location ~* \.(php|env|git|sql)$ {
return 444;
}
location / {
proxy_pass http://127.0.0.1:1234;
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;
}
}
# HTTP → HTTPS ui.example.com
server {
listen 123.45.67.89:80;
server_name ui.example.com;
return 301 https://ui.example.com$request_uri;
}
# HTTPS ui.example.com → Backend
server {
listen 123.45.67.89:443 ssl;
http2 on;
server_name ui.example.com;
access_log /logs/ui.example.com/access_log;
error_log /logs/ui.example.com/error_log;
ssl_certificate /etc/letsencrypt/ui.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/ui.example.com/key.pem;
ssl_trusted_certificate /etc/letsencrypt/ui.example.com/ca.pem;
location ~* \.(php|env|git|sql)$ {
return 444;
}
location / {
proxy_pass http://127.0.0.1:1235;
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;
}
}
Das wars erstmal mit der Installation. Nachdem du Nginx neu gestartet hast, kannst du die Weboberfläche jetzt über https://ui.example.com aufrufen. Dort musst du zuerst einen Server anlegen. Die URL ist http://example.com und den API-Key hast du ja vorhin erzeugt.

Import von Bitly
Shlink bietet nicht nur die Möglichkeit, seine Daten von Bitly zu migrieren, sondern auch von anderen Quellen. Für Bitly benötigt man ein API-Token, das man mit einem Klick in den Einstellungen erzeugen kann.
Dann öffnet man eine Shell und startet mit docker compose exec shlink shlink short-url:import bitly den Import.

Der Befehl fragt nach dem Bitly‑Token und importiert deine Links. Beachte: Klick‑Daten werden nicht importiert, nur die URLs selbst. Spätestens jetzt solltest du dafür sorgen, dass die DNS-Einträge nicht mehr auf Bitly zeigen, sondern auf deinen Server.
Fazit
Der Umzug von Bitly zu Shlink war einfacher als erwartet. Mit Docker Compose, einer sauberen Nginx‑Konfiguration und dem Import‑Tool habe ich das innerhalb weniger Stunden erledigt. Klar, Bitly ist minimal komfortabler, aber um ein paar Links zu kürzen und weiterzuleiten reicht Shlink mehr als aus.
