Shlink is an open-source, API-first URL shortener. You get branded short links on your own domain (like go.example.com/promo), visit statistics (referrers, countries, devices), QR codes, tags, custom slugs, expiry dates and multiple domains. Everything is managed through a REST API, a CLI, or the separate Shlink web client. Self-hosting means your links don’t die if a shortener service shuts down, and visitor data stays with you.

Simpler PHP alternative: YOURLS.

Requirements

  • A small VPS: 1 vCPU and 1 GB RAM handles a lot of redirects.
  • A short domain (or subdomain) pointed at the server with an A record. See DNS records explained.
  • A database: SQLite for small setups; MySQL/MariaDB or PostgreSQL for real use.

Install with Docker Compose

# /opt/shlink/compose.yaml
services:
  shlink:
    image: shlinkio/shlink:stable
    restart: unless-stopped
    environment:
      DEFAULT_DOMAIN: go.example.com
      IS_HTTPS_ENABLED: "true"
      DB_DRIVER: postgres
      DB_HOST: db
      DB_NAME: shlink
      DB_USER: shlink
      DB_PASSWORD: change-me
      GEOLITE_LICENSE_KEY: your-maxmind-key    # optional: visit locations
      TIMEZONE: UTC
    ports:
      - "127.0.0.1:8080:8080"
    depends_on: [db]
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: shlink
      POSTGRES_USER: shlink
      POSTGRES_PASSWORD: change-me
    volumes:
      - ./pgdata:/var/lib/postgresql/data
  web-client:                                 # optional admin UI
    image: shlinkio/shlink-web-client:stable
    restart: unless-stopped
    ports:
      - "127.0.0.1:8081:8080"
cd /opt/shlink && docker compose up -d
docker compose exec shlink shlink api-key:generate    # save the key it prints

GeoLite2: for country and city stats, create a free MaxMind account, and set GEOLITE_LICENSE_KEY. Shlink downloads and updates the database itself.

Nginx with HTTPS

server {
    server_name go.example.com;
    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;
    }
    # listen 443 ssl; certbot --nginx -d go.example.com
}

Serve the web client on a separate hostname (for example shlink-admin.example.com). It’s a static app that talks to the API from your browser using the API key. Restrict it with basic auth or an IP allowlist. See Nginx basic auth and Let’s Encrypt.

Visitor IPs come from X-Forwarded-For. If you also use Cloudflare, pass the real IP through. See real IP behind Cloudflare.

Create short URLs

CLI:

docker compose exec shlink shlink short-url:create https://example.com/landing/spring-sale --custom-slug=spring --tags=campaign
docker compose exec shlink shlink short-url:list
docker compose exec shlink shlink short-url:visits spring

REST API:

curl -X POST https://go.example.com/rest/v3/short-urls \
  -H "X-Api-Key: $SHLINK_KEY" -H "Content-Type: application/json" \
  -d '{"longUrl":"https://example.com/docs","customSlug":"docs","tags":["docs"],"validUntil":"2027-01-01T00:00:00Z"}'

The API makes it easy to create links from scripts, CI, or automation tools like n8n.

Web client: add your server (URL plus API key) and manage links, tags, QR codes and visit charts in the browser.

Useful features

  • Multiple domains: one instance can serve several short domains.
  • Device-specific redirects: send Android, iOS and desktop visitors to different URLs.
  • Max visits and validity dates: links that expire.
  • QR codes: https://go.example.com/spring/qr-code.
  • Not-found redirects: send unknown slugs or the bare domain to your main site, instead of a 404.
  • Bot detection: known crawlers aren’t counted as visits.

Privacy

Shlink can anonymise visitor IPs (enabled by default), and you can disable tracking per link or globally. Mention link tracking in your privacy policy. See Plausible Analytics for privacy-friendly site analytics.

Backups and updates

  • Back up the database (pg_dump). That’s all of the state. See pg_dump.
  • Update with docker compose pull && docker compose up -d. Migrations run automatically on start. See updating self-hosted apps.

Quick reference

Task Command
Generate API key shlink api-key:generate
Create link shlink short-url:create <url> --custom-slug=x
List links shlink short-url:list
Visits shlink short-url:visits <slug>
QR code /<slug>/qr-code

Related: UTM tracking and analytics and Nginx reverse proxy.

  • Self-Hosting
  • Docker
  • Marketing