SSD Nodes Learn 8GB di RAM — $66/anno
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-02

Accorciatore URL self-hosted con Shlink e Docker

Configura Shlink 5.1 su un VPS con Docker Compose: dominio breve, DNS, Postgres, chiavi API, client web, codici QR e statistiche sui clic.

Cosa stai creando

Un accorciatore di URL self-hosted è un piccolo server che trasforma un link lungo in uno breve di tua proprietà e conta ogni clic. Shlink è la scelta giusta: è open source, viene distribuito come immagine Docker e svolge l'intero lavoro con un container e un database. Questa guida lo configura su un VPS dietro un vero dominio breve, con HTTPS, una chiave API, codici QR e statistiche sui clic.

Due componenti lo rendono simile a un accorciatore commerciale. Il server API gestisce i reindirizzamenti e contiene i dati. Il client web è un'app statica separata che comunica con questa API dal browser. Puoi eseguire entrambi oppure eseguire solo l'API e gestirla dalla riga di comando.

I numeri di versione riportati qui erano aggiornati a luglio 2026: Shlink 5.1 e shlink-web-client 4.8.

Prima fai puntare un dominio breve al server

Il dominio è il prodotto. s.example.com/abc123 è il collegamento che le persone vedono, quindi scegli un nome breve e definiscilo prima di installare qualsiasi componente. Shlink salva il dominio con ogni URL breve; se lo cambi in seguito, tutti i collegamenti che hai già distribuito smetteranno di funzionare.

Crea un record DNS A per il dominio breve e fallo puntare all'indirizzo IPv4 pubblico del tuo VPS. Aggiungi anche un record AAAA se il server dispone di IPv6. Verifica quindi che il dominio venga risolto prima di continuare.

dig +short s.example.com A

L'output deve essere l'indirizzo del server. Se è vuoto, il record non è ancora stato propagato e ogni passaggio successivo non riuscirà in modo poco chiaro, perché non è possibile emettere un certificato TLS (transport layer security) per un nome che non viene risolto.

Il file compose

Shlink richiede un database. SQLite è adatto per un test, ma Postgres è la scelta corretta per qualsiasi installazione che si intenda conservare, perché le righe delle visite aumentano e Postgres gestisce meglio gli indici e le scritture concorrenti. Inserisci questo contenuto 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:

Entrambe le porte pubblicate sono associate a 127.0.0.1, quindi nulla è raggiungibile da Internet finché non viene configurato il reverse proxy nella sezione successiva. Docker scrive le proprie regole di inoltro prima del firewall dell'host. Di conseguenza, una semplice riga 8080:8080 esporrebbe l'applicazione anche su un sistema il cui firewall sembra chiuso. L'associazione all'indirizzo di loopback evita questo problema. Lo stesso schema si applica a qualsiasi applicazione eseguita in questo modo ed è descritto più dettagliatamente nella guida a Docker Compose su un VPS.

La password del database proviene da un file .env accanto al file compose. In questo modo non viene inserita nel file 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/.env

Avvialo e monitora l'avvio dell'API.

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

Al primo avvio vengono eseguite le migrazioni del database, quindi l'operazione richiede più tempo rispetto agli avvii successivi. Quando l'avvio è completato, verifica che il servizio risponda localmente.

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

Un 200 indica che l'API è attiva e che la connessione al database funziona. Un 500 in questo punto riguarda quasi sempre il database: DB_PASSWORD in .env non corrisponde a quello con cui è stato creato Postgres, perché l'immagine Postgres legge POSTGRES_PASSWORD solo quando inizializza una directory dati vuota. La modifica successiva della password non ha effetto finché non rimuovi il volume e riavvii il servizio.

Shlink espone HTTP non crittografato sulla porta 8080. TLS deve essere gestito da un reverse proxy. L'impostazione fondamentale consiste nel trasmettere il nome host originale. Shlink determina a quale dominio appartiene un codice breve leggendo l'header Host. Se il proxy lo riscrive, i collegamenti esistenti restituiscono risposte 404 e le statistiche delle visite vengono associate al dominio errato.

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;
    }
}

Emetti quindi il certificato. La procedura completa, incluso il timer per il rinnovo, è disponibile nella guida a Certbot per nginx su Ubuntu 24.04.

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

Nel file compose, IS_HTTPS_ENABLED: "true" fa sì che Shlink riporti https:// negli URL brevi che restituisce. Questa impostazione non abilita TLS da sola. Lasciala false dietro un proxy HTTPS: ogni collegamento restituito dall'API sarà un collegamento http:// che esegue poi un reindirizzamento, con un round trip aggiuntivo e un aspetto errato nel client web.

Creare una chiave API

Nessun client può accedere all'API senza una chiave. Generane una tramite la CLI all'interno del container.

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

Il comando stampa la chiave una sola volta. Copiala subito, perché viene memorizzata sotto forma di hash e non può essere visualizzata nuovamente. shlink api-key:list mostra i nomi e indica se ogni chiave è abilitata, ma non mostra mai la chiave. Revoca una chiave con shlink api-key:disable e il relativo nome.

Ogni chiamata REST trasporta la chiave in un'intestazione X-Api-Key.

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

Un oggetto JSON con una chiave shortUrls indica che la chiave funziona. Un 401 contenente INVALID_API_KEY indica che la chiave è errata, disabilitata o scaduta.

La CLI è il modo più rapido per creare link ed è adatta all'uso negli script.

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 consente di ottenere un link leggibile invece di un codice generato. Gli slug sono univoci per dominio, quindi un secondo tentativo con uno slug già utilizzato non va a buon fine invece di sovrascrivere silenziosamente il primo link. --tag può essere ripetuto; i tag consentono di raggruppare i link per i quali si vorranno in seguito statistiche aggregate.

Elencare gli elementi esistenti, quindi esaminare il traffico di un link.

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

short-url:visits stampa una riga per ogni clic, con la data, il referrer e lo user agent. Le colonne relative a paese e città restano vuote se non si imposta una variabile d'ambiente GEOLITE_LICENSE_KEY, ovvero una chiave MaxMind gratuita che Shlink usa per scaricare il database GeoLite2. Senza questa chiave, le visite vengono comunque registrate, ma non vengono geolocalizzate.

Il client web e i codici QR

Il client web è ora disponibile su 127.0.0.1:8081 e richiede una propria configurazione proxy, oppure un tunnel SSH se si preferisce non pubblicarlo. Al primo caricamento richiede l'URL del server e una chiave API. Inserire https://s.example.com e la chiave generata. Il client conserva entrambi nell'archiviazione del browser e chiama direttamente l'API, quindi nessun dato passa attraverso terze parti.

I codici QR non richiedono alcuna configurazione. Aggiungere /qr-code a qualsiasi URL breve e l'API restituisce l'immagine.

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

size indica la larghezza in pixel e accetta valori da 50 a 1000, con 300 come valore predefinito. format è png oppure svg. margin indica lo spazio libero attorno al codice, espresso in pixel; l'immagine finale misura la dimensione più il doppio del margine. Aggiungere errorCorrection=Q per ottenere un codice che continui a essere letto anche se stampato in piccolo o parzialmente coperto.

Mantienilo in esecuzione

Un servizio di abbreviazione può smettere di funzionare senza generare errori visibili. I link non reindirizzano più e nessuno lo segnala, perché chi ha fatto clic presume che il link non sia più valido. Configura un controllo di disponibilità su un URL breve reale, non sulla home page, e genera un avviso per qualsiasi risposta che non sia un reindirizzamento. Un'istanza self-hosted di Uptime Kuma è adatta a questo scopo e può controllare un codice di stato specifico.

Esegui il backup del database, non del container. Un comando lo esporta.

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

Questo file e il file compose consentono di ricreare l'intero servizio su un nuovo server. Gli aggiornamenti sono sudo docker compose pull seguiti da sudo docker compose up -d e Shlink esegue le nuove migrazioni all'avvio. Crea il dump prima di eseguire il pull, perché una migrazione non può essere annullata.

FAQ

Shlink confronta un codice breve con il dominio nell'header Host. Un proxy che invia il proprio nome o un indirizzo interno induce Shlink a cercare quel codice in un dominio che non contiene link, quindi restituisce 404. Imposta proxy_set_header Host $host; nel blocco location di nginx e ricarica il proxy. I link tornano subito a funzionare, senza riavviare il container.

Ho bisogno di Postgres o è sufficiente SQLite?

SQLite è adatto per provare Shlink e non richiede un secondo container. Passa a Postgres prima di pubblicare link importanti, perché le righe delle visite aumentano a ogni clic e SQLite serializza le scritture. In seguito, il passaggio richiede l'esportazione e la reimportazione dei link, quindi scegliere Postgres fin dall'inizio evita questa migrazione.

Posso recuperare una chiave API che ho dimenticato di copiare?

No. Shlink memorizza un hash della chiave, quindi api-key:list mostra i nomi e lo stato, ma non il valore. Genera una sostituzione con shlink api-key:generate, incollala nel client web, quindi disabilita quella precedente con shlink api-key:disable per impedirne l'uso.

Perché le colonne dei Paesi sono vuote nelle statistiche delle visite?

La geolocalizzazione richiede il database GeoLite2, che Shlink scarica solo quando fornisci un GEOLITE_LICENSE_KEY. La chiave è gratuita e si ottiene da MaxMind. Aggiungila alla sezione dell'ambiente, ricrea il container e le nuove visite verranno geolocalizzate. Le visite registrate prima resteranno vuote finché non esegui shlink visit:locate.

Mantieni il dominio e sposta i dati. Esegui il dump del database con pg_dump, copia il dump e il file compose sul nuovo server, avvia lo stack, quindi ripristina il dump nel database vuoto prima che arrivi traffico reale. Modifica il record DNS per ultimo. I codici brevi e la relativa cronologia delle visite vengono mantenuti, perché tutto risiede nel database.

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