Journal indexDockup / field note
Note / self-host-shlink

How to Self-Host Shlink in 2026: Domains, API Keys and Statistics

A practical Shlink self-hosting guide covering Docker, ports, persistent data, TLS, security, backups and the failures that block production use. Step by step.

Self-hosting Shlink becomes interesting at the first redeploy, not the first docker run. If generated links use HTTP or migrations cannot reach the database, Docker can still report a perfectly healthy process. The deployment below is organized around observable behavior: create a short URL through the API, follow its redirect, record visits and inspect statistics from the web client.

The intended job of Shlink is explicit: API-first link shortener with statistics. That description tells us what must stay public, what should remain private and what a backup has to reconstruct.

What Shlink depends on

Process health and product health are separate for Shlink. Port 8080 may answer while the user-facing transaction still fails. The network contract for Shlink is Postgres or MariaDB plus optional Redis for production. Keep private endpoints on internal DNS, permit only required outbound calls and give Shlink a scoped service credential.

Use this readiness exercise after meaningful configuration changes: create a short URL through the API, follow its redirect, record visits and inspect statistics from the web client. Keep expensive external checks out of liveness probes so a provider outage does not cause a restart loop. Capacity work should track redirect throughput, database writes, geolocation downloads and cache behavior, which is closer to Shlink's real pressure than page requests.

Volumes are only the first recovery layer

No writable application state is expected inside the standard Shlink image. Preserve the database, API keys and any imported visit data, including the pinned digest and reviewed route configuration, rather than backing up an empty container filesystem.

Create Shlink from scratch on another host and verify that domains, short codes, tags and visit records return and every sampled short URL redirects identically. If a separate database, room server or authentication layer is added, give that component its own explicit recovery owner. The Git-to-production guide shows how a reproducible artifact replaces a container backup.

Record the rebuild command and known-output test with the release. A stateless recovery plan succeeds by reproducing behavior from trusted inputs; it should not depend on copying an opaque running container.

Protect the valuable part of Shlink

A secure Shlink deployment starts by removing authority. Avoid exposing the REST API key or changing the public domain after links ship; instead, keep API keys out of browser code, use HTTPS and restrict administration while leaving redirects public.

DEFAULT_DOMAIN is configuration rather than a secret; keep its value explicit while protecting the separate credentials used by Shlink. Restrict administrative routes, use private DNS for dependencies and review every bind mount. When logs are shipped centrally, filter secrets and private content before they leave the server.

Turn the Shlink smoke test into a release check

For Shlink, define a known-good transaction before launch: create a short URL through the API, follow its redirect, record visits and inspect statistics from the web client. Put its prerequisites, expected response and cleanup steps in version control without secret values. Pin the image used to establish that reference.

Use the transaction to validate a replacement and an independent restore. The restored service is acceptable only when domains, short codes, tags and visit records return and every sampled short URL redirects identically. At the same time, observe redirect throughput, database writes, geolocation downloads and cache behavior and turn the slowest or most constrained part into a service-level alert.

The gate also needs a negative case: temporarily deny the test identity access to Postgres or MariaDB plus optional Redis for production. Confirm that Shlink produces an actionable error while preserving data, restore the valid condition and repeat the known-good transaction. Keeping both results prevents a superficial health endpoint from becoming the only production evidence.

Start Shlink without hiding the moving parts

The following command makes the container boundary visible without pretending to provision every external service.

docker run -d \
  --name shlink \
  --restart unless-stopped \
  -p 127.0.0.1:8080:8080 \
  -e DEFAULT_DOMAIN=go.example.com \
  shlinkio/shlink:stable

Before opening ingress, inspect the resolved environment, mounts and listener. Add the reviewed connection settings for Postgres or MariaDB plus optional Redis for production; use private names for private services. A successful launch ends when you can create a short URL through the API, follow its redirect, record visits and inspect statistics from the web client, not when docker ps prints Up.

Give Shlink one canonical address

The public boundary for Shlink should be one canonical hostname, automatic TLS and one internal target on 8080. Set DEFAULT_DOMAIN and IS_HTTPS_ENABLED before creating short URLs so clients return to an address the service recognizes.

If the acceptance transaction fails, classify the first error. DNS, certificate and 502 problems belong to the TLS validation checklist. The condition “generated links use HTTP or migrations cannot reach the database” belongs to the application side after a request has successfully reached Shlink.

Diagnose a healthy-looking Shlink

For Shlink, monitor a transaction rather than a process: create a short URL through the API, follow its redirect, record visits and inspect statistics from the web client. Combine its latency and error rate with redirect throughput, database writes, geolocation downloads and cache behavior so an alert identifies the constrained component.

The upgrade rehearsal must cover that database migrations and API compatibility should be staged because published short links cannot wait for manual repair. Restore, migrate and run the transaction before production replacement. If generated links use HTTP or migrations cannot reach the database, do not erase data to make startup green; compare version, variables, mounts and dependency reachability in that order.

Keep Shlink explicit while Dockup handles routing

Dockup's one-click Shlink deployment should make replacement safe: the route continues to target 8080, secrets are not baked into the image and persistent paths return on the new container. The same deployment can run on Dockup compute or an attached machine.

Complete the app-specific work by connecting and testing Postgres or MariaDB plus optional Redis for production, applying the canonical public address and running this acceptance check: create a short URL through the API, follow its redirect, record visits and inspect statistics from the web client. Add the restore result to the runbook before real users arrive.

Frequently asked questions

What does Shlink need for a production deployment?

Route the Shlink container on port 8080 through one HTTPS origin. The supporting network requirement is Postgres or MariaDB plus optional Redis for production. Do not call Shlink ready until you can create a short URL through the API, follow its redirect, record visits and inspect statistics from the web client.

Which Shlink data belongs in a backup?

The standard Shlink image has no required application-data mount. Preserve its deployment configuration and back up any connected state separately; recovery passes when domains, short codes, tags and visit records return and every sampled short URL redirects identically.

Does Shlink require HTTPS behind a reverse proxy?

Use HTTPS for the public Shlink origin and keep port 8080 on the internal route. Apply the Shlink setting correctly: set DEFAULT_DOMAIN and IS_HTTPS_ENABLED before creating short URLs. For Shlink, HTTPS protects credentials or user content in transit and keeps origin-sensitive client behavior consistent.

How should a Shlink upgrade be tested?

Restore current Shlink state into an isolated deployment, apply the candidate version and repeat its acceptance transaction. Pay particular attention because database migrations and API compatibility should be staged because published short links cannot wait for manual repair. Keep the previous Shlink image until its data-migration and rollback boundary are understood.