Installer un raccourcisseur d’URL avec Shlink et Docker
Installez Shlink 5.1 sur un VPS avec Docker Compose : domaine court, HTTPS, Postgres, clé d’API, client web, QR codes et statistiques de clics.
Ce que vous allez construire
Un raccourcisseur d’URL auto-hébergé est un petit serveur qui transforme un lien long en un lien court que vous contrôlez et qui compte chaque clic. Shlink est le bon choix : il est open source, il est distribué sous forme d’image Docker et il fournit toutes les fonctions nécessaires dans un conteneur avec une base de données. Ce guide l’installe sur un VPS derrière un véritable domaine court, avec HTTPS, une clé d’API, des codes QR et des statistiques de clics.
Deux composants lui donnent le fonctionnement d’un raccourcisseur commercial. Le serveur d’API gère les redirections et conserve les données. Le client web est une application statique distincte qui communique avec cette API depuis votre navigateur. Vous pouvez exécuter les deux, ou exécuter uniquement l’API et la piloter depuis la ligne de commande.
Les numéros de version indiqués ici correspondent aux versions disponibles en juillet 2026 : Shlink 5.1 et shlink-web-client 4.8.
Pointez d’abord un domaine court vers le serveur
Le domaine est le produit. s.example.com/abc123 est le lien que les utilisateurs voient. Choisissez donc un nom court avant d’installer quoi que ce soit. Shlink enregistre le domaine avec chaque URL courte. Si vous le modifiez plus tard, tous les liens déjà distribués cesseront de fonctionner.
Créez un enregistrement DNS A pour le domaine court. Faites-le pointer vers l’adresse IPv4 publique de votre VPS. Ajoutez également un enregistrement AAAA si le serveur dispose d’IPv6. Vérifiez ensuite sa résolution avant de continuer.
dig +short s.example.com ALa sortie doit être l’adresse de votre serveur. Si elle est vide, l’enregistrement ne s’est pas encore propagé. Toutes les étapes suivantes échoueront alors de manière difficile à diagnostiquer, car il est impossible d’émettre un certificat TLS (transport layer security) pour un nom qui ne se résout pas.
Le fichier Compose
Shlink a besoin d’une base de données. SQLite convient pour un test, mais Postgres est le bon choix pour tout ce que vous prévoyez de conserver, car les lignes de visites s’accumulent et Postgres gère mieux les index et les écritures concurrentes. Placez ceci dans /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:Les deux ports publiés sont liés à 127.0.0.1. Rien n’est donc accessible depuis Internet tant que le reverse proxy de la section suivante n’est pas en place. Docker écrit ses propres règles de redirection avant le pare-feu de l’hôte. Une simple ligne 8080:8080 exposerait donc l’application, même si le pare-feu de la machine semble fermé. La liaison à l’adresse loopback évite ce problème. Le même principe s’applique à toute application exécutée de cette manière. Il est expliqué plus en détail dans le guide Docker Compose sur un VPS.
Le mot de passe de la base de données vient d’un fichier .env placé à côté du fichier Compose. Il ne se retrouve donc jamais dans le fichier YAML.
sudo mkdir -p /opt/shlink
printf 'DB_PASSWORD=%s\n' "$(openssl rand -base64 24)" | sudo tee /opt/shlink/.env
sudo chmod 600 /opt/shlink/.envDémarrez-la et surveillez le démarrage de l’API.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkLe premier démarrage exécute les migrations de la base de données. Il prend donc plus de temps que les suivants. Une fois le démarrage terminé, vérifiez que le service répond localement.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/healthUn 200 signifie que l’API est active et que la connexion à la base de données fonctionne. Un 500 à cet endroit concerne presque toujours la base de données. Le DB_PASSWORD dans .env ne correspond pas à celui utilisé lors de la création de Postgres, car l’image Postgres ne lit POSTGRES_PASSWORD que lorsqu’elle initialise un répertoire de données vide. Modifier le mot de passe ultérieurement n’a aucun effet tant que vous ne supprimez pas le volume et ne redémarrez pas le service.
Terminer TLS en amont
Shlink sert du HTTP non chiffré sur le port 8080. TLS doit être géré par un reverse proxy. Le point essentiel consiste à transmettre le nom d’hôte d’origine. Shlink détermine à quel domaine un code court appartient en lisant l’en-tête Host. Si le proxy le réécrit, les liens existants renvoient des réponses 404 et les statistiques de visite sont associées au mauvais domaine.
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;
}
}Émettez ensuite le certificat. La procédure complète, y compris le timer de renouvellement, se trouve dans le guide Certbot pour nginx sur Ubuntu 24.04.
sudo certbot --nginx -d s.example.comIS_HTTPS_ENABLED: "true" dans le fichier compose indique à Shlink d’inclure https:// dans les URL courtes qu’il renvoie. Ce paramètre n’active pas TLS à lui seul. Laissez-le false derrière un proxy HTTPS : chaque lien renvoyé par l’API est alors un lien http:// qui est ensuite redirigé. Cela ajoute un aller-retour et donne un résultat incorrect dans le client web.
Créer une clé d’API
Aucun client ne peut communiquer avec l’API sans clé. Générez-en une avec la CLI dans le conteneur.
sudo docker compose exec shlink shlink api-key:generate --name "web client"La commande affiche la clé une seule fois. Copiez-la maintenant, car elle est stockée sous forme de hash et ne peut plus être affichée. shlink api-key:list affiche les noms et indique si chaque clé est activée, mais n’affiche jamais la clé elle-même. Révoquez une clé avec shlink api-key:disable et son nom.
Chaque appel REST transmet la clé dans un en-tête X-Api-Key.
curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urlsUn objet JSON contenant une clé shortUrls indique que la clé fonctionne. Un 401 contenant INVALID_API_KEY indique que la clé est incorrecte, désactivée ou arrivée à expiration.
Créer des liens courts depuis la ligne de commande
La CLI est le moyen le plus rapide de créer des liens. Elle s’intègre aussi facilement aux scripts.
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 vous fournit un lien lisible au lieu d’un code généré. Les slugs sont uniques par domaine. Une deuxième tentative avec un slug déjà utilisé échoue au lieu d’écraser silencieusement le premier lien. Vous pouvez répéter --tag. Les tags servent à regrouper les liens pour lesquels vous voudrez obtenir des statistiques combinées ultérieurement.
Listez les éléments existants, puis examinez le trafic d’un lien.
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsshort-url:visits affiche une ligne par clic, avec la date, le referer et le user agent. Les colonnes du pays et de la ville restent vides tant que vous n’avez pas défini une variable d’environnement GEOLITE_LICENSE_KEY. Il s’agit d’une clé MaxMind gratuite que Shlink utilise pour télécharger la base de données GeoLite2. Sans cette clé, les visites sont toujours enregistrées, mais leur emplacement n’est pas déterminé.
Le client web et les codes QR
Le client web est maintenant disponible sur 127.0.0.1:8081 et nécessite sa propre entrée de proxy, ou un tunnel SSH si vous préférez ne pas le publier. Au premier chargement, il demande l’URL du serveur et une clé API. Saisissez https://s.example.com et la clé que vous avez générée. Le client conserve ces deux éléments dans le stockage du navigateur et appelle directement votre API. Aucune donnée ne transite par un tiers.
Les codes QR ne nécessitent aucune configuration. Ajoutez /qr-code à n’importe quelle URL courte. L’API renvoie l’image.
https://s.example.com/docs/qr-code?size=500&format=svg&margin=20size correspond à la largeur en pixels et accepte une valeur comprise entre 50 et 1000, avec 300 par défaut. format vaut png ou svg. margin correspond à la marge autour du code, en pixels. L’image finale mesure la taille du code plus deux fois la marge. Ajoutez errorCorrection=Q pour obtenir un code qui reste lisible lorsqu’il est imprimé en petit ou partiellement masqué.
Maintenir le service en fonctionnement
Un shortener peut tomber en panne sans générer d’erreur visible. Les liens cessent de rediriger et personne ne vous prévient, car la personne qui a cliqué pense que le lien est mort. Configurez un contrôle de disponibilité sur une URL courte réelle plutôt que sur la page d’accueil, et déclenchez une alerte pour tout ce qui n’est pas une redirection. Une instance Uptime Kuma auto-hébergée convient bien à cet usage et peut vérifier un code d’état précis.
Sauvegardez la base de données, pas le conteneur. Une commande suffit pour l’exporter.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzCe fichier et votre fichier compose permettent de reconstruire l’ensemble du service sur un nouveau serveur. Les mises à niveau consistent à exécuter sudo docker compose pull, puis sudo docker compose up -d, et Shlink exécute toutes les nouvelles migrations au démarrage. Effectuez l’export avant le pull, car une migration ne peut pas être annulée.
FAQ
Pourquoi mes liens courts renvoient-ils une erreur 404 après l’ajout d’un reverse proxy ?
Shlink compare un code court au domaine indiqué dans l’en-tête Host. Un proxy qui envoie son propre nom ou une adresse interne oblige Shlink à rechercher ce code sous un domaine qui ne contient aucun lien. Il renvoie donc une erreur 404. Définissez proxy_set_header Host $host; dans le bloc location de nginx, puis rechargez le proxy. Les liens fonctionnent immédiatement, sans redémarrer le conteneur.
Ai-je besoin de Postgres, ou SQLite suffit-il ?
SQLite convient pour tester Shlink et ne nécessite pas de second conteneur. Passez à Postgres avant de publier des liens importants, car le nombre de lignes de visites augmente à chaque clic et SQLite sérialise les écritures. Une migration ultérieure nécessite d’exporter puis de réimporter vos liens. Choisir Postgres dès le départ vous évite donc cette migration.
Puis-je récupérer une clé d’API que j’ai oublié de copier ?
Non. Shlink stocke un hash de la clé. api-key:list affiche donc les noms et l’état, mais jamais sa valeur. Générez une clé de remplacement avec shlink api-key:generate, collez-la dans le client web, puis désactivez l’ancienne avec shlink api-key:disable pour qu’elle cesse de fonctionner.
Pourquoi les colonnes de pays sont-elles vides dans mes statistiques de visites ?
La géolocalisation nécessite la base de données GeoLite2. Shlink la télécharge uniquement si vous lui fournissez un GEOLITE_LICENSE_KEY. La clé est gratuite et fournie par MaxMind. Ajoutez-la à la section d’environnement, recréez le conteneur, puis les nouvelles visites seront géolocalisées. Les visites enregistrées auparavant restent sans pays jusqu’à l’exécution de shlink visit:locate.
Comment déplacer Shlink vers un autre serveur ?
Conservez le domaine et déplacez les données. Exportez la base de données avec pg_dump, copiez le dump et le fichier compose vers le nouveau serveur, démarrez la stack, puis restaurez le dump dans la base de données vide avant l’arrivée du trafic réel. Modifiez l’enregistrement DNS en dernier. Les codes courts et leur historique de visites sont conservés, car toutes les données se trouvent dans la base de données.