SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

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 configurando

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 dominio breve reale, 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 redirect e conserva i dati. Il client web è un'applicazione statica separata che comunica con quell'API dal browser. Puoi eseguirli entrambi oppure usare solo l'API e controllarla dalla riga di comando.

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

Punta prima un dominio breve al server

Il dominio è il prodotto. s.example.com/abc123 è il collegamento che vedranno gli utenti, 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 già distribuiti 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. Quindi verifica che il dominio venga risolto prima di continuare.

dig +short s.example.com A

L'output deve corrispondere all'indirizzo del server. Se è vuoto, il record non è ancora stato propagato. Tutti i passaggi successivi falliranno 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 ai test, ma Postgres è la scelta corretta per qualsiasi installazione da conservare, perché le righe delle visite aumentano nel tempo 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 configuri il reverse proxy nella sezione successiva. Docker inserisce le proprie regole di inoltro prima del firewall dell'host. Di conseguenza, una semplice regola 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 vale per 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, quindi 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 il servizio è pronto, verifica che 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 al valore con cui è stato creato Postgres, perché l'immagine Postgres legge POSTGRES_PASSWORD soltanto quando inizializza una directory dati vuota. La modifica successiva della password non ha effetto finché non rimuovi il volume e riavvii il servizio.

Shlink fornisce HTTP non cifrato sulla porta 8080. TLS deve essere gestito da un reverse proxy. L’impostazione importante 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 link esistenti restituiscono una risposta 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;
    }
}

Quindi emettere il certificato. La procedura completa, inclusa la configurazione del timer di 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 inserisca https:// negli URL brevi che restituisce. Non abilita TLS autonomamente. Lasciare false dietro un proxy HTTPS fa sì che ogni link restituito dall’API sia un link http:// che esegue quindi un redirect. Questo aggiunge un round trip e appare errato nel client web.

Crea una chiave API

Senza una chiave, nessun client può comunicare con l'API. Generane una tramite la CLI all'interno del container.

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

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

Ogni chiamata REST trasmette 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 che contiene INVALID_API_KEY indica che la chiave è errata, disabilitata o scaduta.

La CLI è il metodo più rapido per creare link ed è adatta all'automazione tramite 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 restituisce un link leggibile invece di un codice generato. Gli slug sono univoci per dominio, quindi un secondo tentativo di usare uno slug già occupato non riesce e non sovrascrive il primo link. --tag può essere ripetuto; i tag servono a raggruppare i link per i quali si vorranno ottenere statistiche aggregate in seguito.

Elenca ciò che esiste, quindi controlla 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 data, referrer e user agent. Le colonne relative a paese e città restano vuote se non imposti una variabile d'ambiente GEOLITE_LICENSE_KEY, che corrisponde a una chiave MaxMind gratuita usata da Shlink per scaricare il database GeoLite2. Senza questa variabile, le visite vengono comunque registrate, ma non ne viene determinata la posizione.

Il client web e i codici QR

Il client web è ora disponibile su 127.0.0.1:8081 e richiede una propria configurazione del proxy, oppure un tunnel SSH se si preferisce non pubblicarlo. Al primo avvio 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 da terze parti. Separare l’interfaccia dall’API è un modello da osservare, perché è lo stesso che consente a Halcyon di presentare una libreria Jellyfin come un videonoleggio degli anni 1990 senza modificare il media server sottostante.

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 è la larghezza in pixel e accetta valori da 50 a 1000, con 300 come valore predefinito. format è png o svg. margin è lo spazio vuoto attorno al codice, espresso in pixel, e l’immagine finale misura la dimensione più il doppio del margine. Aggiungere errorCorrection=Q per ottenere un codice che continui a essere leggibile anche se stampato in piccolo o parzialmente coperto.

Tenerlo in esecuzione

Un servizio di accorciamento URL può interrompersi senza produrre errori evidenti. I link smettono di effettuare il reindirizzamento e nessuno ti avvisa, perché chi ha fatto clic pensa 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 diversa da un reindirizzamento. Un'istanza self-hosted di Uptime Kuma è adatta a questo scopo e può verificare uno specifico codice di stato.

Esegui il backup del database, non del container. Un comando ne crea il dump.

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

Quel file, insieme al file compose, consente di ricreare l'intero servizio su un nuovo server. Ogni applicazione sul server deve avere una propria versione di questa coppia di file. Una libreria fotografica è il caso più complesso, perché PhotoPrism e Immich conservano gli originali su disco oltre alle righe nel database: un semplice dump non consente di ripristinare nulla. Gli aggiornamenti vengono eseguiti tramite sudo docker compose pull seguito da sudo docker compose up -d, e Shlink applica le nuove migrazioni all'avvio. Crea il dump prima di eseguire il pull, perché una migrazione non può essere annullata.

FAQ

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

Ho bisogno di Postgres oppure è sufficiente SQLite?

SQLite è sufficiente 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 dovresti esportare e reimportare i link, quindi scegliere Postgres 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 sostitutiva 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 gli fornisci un GEOLITE_LICENSE_KEY. La chiave è gratuita e viene rilasciata da MaxMind. Aggiungila alla sezione dell'ambiente, ricrea il container e le nuove visite verranno geolocalizzate. Le visite registrate in precedenza resteranno senza dati finché non esegui shlink visit:locate.

Mantieni il dominio e sposta i dati. Esporta il 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 cronologia delle visite verranno mantenuti, perché tutti i dati risiedono nel database.

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