curl -fsSL https://shortlink/slug

Posted on May 21, 2022
tl;dr: Long blog post URLs are a thing of the past. We'll set up shlink on Ubuntu, using PostgreSQL as a DB and nginx as a reverse proxy and SSL/TLS offloader

Hey Victor,

How on earth can I replace {bit,cutt}.ly and such? I want something selfhosted, and a terrible guide on how to do it!

  Hold my really long links, I’m going in!  

Getting started

  1. Install Docker. In this guide we’re using Ubuntu as our OS.

Step 1. Decide on your database type. The default is sqlite, but we’ll use postgres here.

Step 2. Run the postgres container (we actually need to run the database, not just decide on one).
We’ll make sure to only expose the port on, that way we don’t need to mess around with any firewalls.

docker run \
    --name postgresql \
    --restart unless-stopped \
    -e POSTGRES_USER=goodusername \
    -e POSTGRES_PASSWORD=shittypassword \
    -p \
    -v /data:/var/lib/postgresql/data \
    -d postgres

Step 3. Get the IP of the container so we can tell shlink to connect to the DB

docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgresql

Step 4. Run shlink container. We’ll use the domain name for the short links.

docker run -d \
    --name shlink \
    --restart unless-stopped \
    -p \
    -e \
    -e IS_HTTPS_ENABLED=true \
    -e DB_DRIVER=postgres \
    -e DB_USER=goodusername \
    -e DB_PASSWORD=shittypassword \
    -e DB_HOST= \

Step 5. Generate an API key for shlink.

docker exec -it shlink shlink api-key:generate

Step 6. This is actually optional - we can generate links via CLI. Run the web client to manage the instance.

docker run -d \
    --name shlink-web-client \ 
    -p \

Step 7. We should serve this over HTTPS, so unless we already have a SSL keypair, let’s get certbot and issue it for us. By now you should’ve set up your DNS record already.

snap install certbot --classic
certbot certonly --standalone -d

Step 8. Set up nginx or your favorite reverse proxy.

apt install nginx

Since this server will only serve one purpose, we’ll just use the default config file, /etc/nginx/sites-enabled/default:

server {
 # just redirect all HTTP to HTTPS
 listen 80 default_server;
 return 301 https://$host$request_uri;

server {
 listen 443 default_server http2 ssl;
 ssl_certificate /etc/letsencrypt/live/;
 ssl_certificate_key /etc/letsencrypt/live/;

 location /panel {
  allow; #my IP
  deny all;

 location / {


Please note - while IP white/blacklisting works - I’ll actually protect the panel using Cloudflare Access instead, for full … 🚨 LinkedIn Buzzword Alert 🚨 Zero Trust controls.
That’d be far too much of a spin-off from getting this up and running though, so I won’t cover it here.

Step 9. Reload nginx

nginx -s reload

Step 10. Setting it up.
Is dead simple, really. Browse to your panel (as configured in your nginx site config in step 8), and set up the Name (anything), URL ( in my case) and the API key gathered from step 5 in this guide.

After that, we’re done!