Shlink

The definitive self-hosted URL shortener

URL Shorteners ★ 5.3k stars Medium setup MIT

Shlink is a self-hosted URL shortener with a powerful REST API, detailed visit analytics, QR codes, and a separate web client. It supports custom domains and tags for organizing links.

Key features

  • Powerful REST API
  • Detailed visit analytics
  • QR code generation
  • Multi-domain support

Pros & cons

Strengths

  • Excellent API
  • Geolocation analytics

Trade-offs

  • Web UI is a separate component

Shlink replaces

Last reviewed Sep 13, 2026 · 850 words

Shlink is two containers, and the second one is optional. The server is a PHP application that exposes a REST API and redirects short links; the web client is a separate static app that talks to that API from your browser. People who skip the web client and never notice are the ones Shlink was built for: developers who create links from scripts, CI pipelines or a n8n flow and want per-link visit stats with country, browser and referrer. If you want a shortener you administer by clicking, YOURLS is older and simpler and I say so below. If you want the best API in the category and analytics that stand up, Shlink is the pick, at 5,261 GitHub stars under MIT.

The server container and its three decisions

services:
  shlink:
    image: shlinkio/shlink:stable
    environment:
      DEFAULT_DOMAIN: s.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
    ports:
      - "127.0.0.1:8080:8080"
    restart: unless-stopped
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: shlink
      POSTGRES_USER: shlink
      POSTGRES_PASSWORD: change-me
    volumes:
      - ./pgdata:/var/lib/postgresql/data

Decision one is the domain. DEFAULT_DOMAIN is what short links are built on, so it should be the shortest domain you own; IS_HTTPS_ENABLED must be true behind a TLS-terminating proxy or generated links come out as http://. Decision two is the database. SQLite works and is what Shlink defaults to, and it is fine for a personal shortener; the catalogue tags Shlink as Postgres for a reason, because every visit is a row, a link that gets shared once can write 10,000 rows in an afternoon, and Postgres handles that without locking the redirect path. The Postgres for everything argument applies. Decision three is geolocation, covered next. Shlink is Medium difficulty because of these choices, not because any one of them is hard; it needs about 256 MB of RAM.

Analytics need a free MaxMind key

Country and city on visits come from the GeoLite2 database, which MaxMind gives away but only with an account and a licence key. Without GEOLITE_LICENSE_KEY every visit is recorded but shows no location. Sign up at maxmind.com, generate a key, set the variable, and Shlink downloads and refreshes the database itself. Referrer, user agent and timestamp are tracked regardless. Note that this makes Shlink a visitor-tracking system for whoever clicks your links, and it does not anonymise by default; ANONYMIZE_REMOTE_ADDR is true out of the box so IPs are truncated before storage, and I would leave it that way and mention it in a privacy note if the links are public.

Get an API key, then decide whether you need the UI

docker compose exec shlink shlink api-key:generate

That key is your credential for everything: the REST API, the web client, the CLI. Creating a link is one request:

curl -X POST https://s.example.com/rest/v3/short-urls \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"longUrl": "https://example.com/some/long/path", "customSlug": "launch"}'

Tags, expiry dates, max visits, custom slugs, per-device redirect rules and QR codes are all parameters or endpoints on the same API. The web client (shlinkio/shlink-web-client) is a separate container serving a static single-page app; you point it at your server URL and key from the browser, or bake a servers.json into it. Run it on a different hostname from the short domain, since the short domain should serve nothing but redirects. Or do not run it at all and use the CLI inside the container, which covers every operation.

ShlinkYOURLSKutt
LanguagePHPPHPTypeScript
DatabasePostgres, MySQL, MariaDB, SQLiteMySQLPostgres and Redis
APIFull REST, versionedSimple, key-basedREST
Admin UISeparate containerBuilt inBuilt in
Visit analyticsCountry, city, browser, referrerClick counts, basicBasic
Multiple domainsYesNoYes
UsersSingle tenant, many API keysSingle adminMulti-user with sign-up

YOURLS has been around longer and is the right answer for one person who wants a UI, a MySQL database they already have, and no interest in scripting. Kutt is the one with user accounts, if you are running a shortener for a team that signs in. Shlink wins on API quality, multi-domain and analytics depth, and it is the only one of the three I would put a public campaign behind. The URL shorteners category lists the rest of the field.

What I'd do

Shlink server on Postgres, on the shortest domain you own, HTTPS on, MaxMind key set, one API key per client that creates links. Skip the web client until you find yourself wanting to browse stats, then run it on an admin hostname behind your SSO. Back up the Postgres volume and the environment file; the container itself holds nothing. Leaving Bitly for this costs one afternoon and removes the per-link pricing and the tracking you did not ask for.

Compare Shlink

11 head-to-head comparisons.

Similar url shorteners apps