Optimize Your URL Sharing with Shlink

Shlink is a link shortener shown in a generated image of a laptop

Eventually, you’ll want to make use of a link shortener. There are lots of reasons: convenience in sharing and privacy are just two top reasons. If you’ve ever shared a Microsoft Teams invite via text message, you’ll wish you had a link shortener handy. But beware: the “free” link shorteners, like bit.ly, are harvesting your clicks and invading your privacy.

It’s best to just run a shortener yourself. My choice is shlink which bills itself as the “definitive self-hosted URL shortener.” And it is, indeed, wonderful at what it does.

shlink can be remarkably easy to deploy. All you need to get started is a Docker environment of some kind and a domain you control. You need deploy only a single container to run the shlink back end which you can access via the public and lovely shlink web UI.

That’s how I got started…but that deployment is not the focus of this post. While the simplest deployment (just a single container and the free hosted web app) is fully functional, I had two issues with that architecture.

First, the all-in-one back end shlink container also contains the SQL database that stores the shortened links. That means you cannot easily update that container when and if you need to, say, for a security update. shlink is PHP based and as anyone who runs PHP knows, you really must be able to update quickly and easily when the next and inevitable CVSS score of 9.8 happens in PHP.

Second, I’m a fan of self-hosting. Sure, it costs more and is more complex. But it’s the ultimate in control and safety. Self hosting, done correctly, is also the best teaching environment you’ll ever encounter.

Here’s a schematic of how I’ve installed shlink.

A schematic of a possible installation of shlink.
Using an external database for shlink (click to enlarge)

And here are some notes on this kind of deployment:

  • Never expose a container or VM to the public internet. Here, shlink is running in Docker on an Ubuntu server on a Proxmox node, safely behind pfSense’s HAProxy. (In fact, it’s double-proxied).
  • mySQL is installed on the VM and the shlink container is configured to use the “external” database.
  • Apache is installed as the proxy for the two containers (shlink’s back end and the web client).
  • TLS certificates are from Let’s Encrypt. For convenience, I created a single cert with both the back end and the web client’s SNIs included in the cert.

If you’ve read this far, I assume you’ve got a virtualization environment, know how to set up your network edge to secure traffic inbound to shlink, TLS certificates, Linux VMs, MySQL, Docker and Apache (the shlink docs appear to favor nginx but I am an Apache2 fanboy).

So, here are three “starter” configurations you might work from to build a shlink environment like the one above.

First, is the Docker Compose file. Then I’ve posted the two Apache2 virtual hosts you would enable to proxy inbound traffic to shlink. If you use them as models, be sure to change the invalid names and credentials in the versions posted here.

version: '3.8'

services:
  shlink_app:
    image: ghcr.io/shlinkio/shlink
    container_name: shlink_backend
    network_mode: "host"
    environment:
      - DEFAULT_DOMAIN=s.example.com
      - IS_HTTPS_ENABLED=true
      - GEOLITE_LICENSE_KEY=YOUR_KEY_HERE
      - DB_DRIVER=mysql
      - DB_USER=shlink
      - DB_PASSWORD=YOUR_PASSWORD_HERE
      - DB_HOST=127.0.0.1
      - SHELL_VERBOSITY=3
    restart: always

  shlink_web_client:
    image: ghcr.io/shlinkio/shlink-web-client
    container_name: shlink_web_client
    ports:
      - "8088:8080"
    environment:
      - SHELL_VERBOSITY=3
    restart: always

Here’s an Apache virtual host definition for the shlink backend.

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName s.example.com
    ServerAlias shlink-backend.example.com

    ProxyPreserveHost On
    ProxyRequests Off

    # Pass the client IP address to Shlink for geolocation.
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"

    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    # SSL/TLS configuration
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/shlink.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/shlink.example.com/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

And, finally, here’s an Apache virtual host definition for the web client container.

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName shlink.example.com 
    ServerAlias shlinkui.example.com

    ProxyPreserveHost On
    ProxyRequests Off

    # Pass the client IP address to Shlink for geolocation.
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"

    ProxyPass / http://127.0.0.1:8088/
    ProxyPassReverse / http://127.0.0.1:8088/

    # SSL/TLS configuration
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/shlink.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/shlink.example.com/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>


Posted

in

, , ,

by

Comments

2 responses to “Optimize Your URL Sharing with Shlink”

  1. Anon E. Moose Avatar
    Anon E. Moose

    Good cyber hygiene requires us to not click on links unless we are confident the destination is safe–which requires us to be able to read the URL.

    The UK’s NCSC explicitly recommends businesses NOT use shortened URLS:

    https://www.ncsc.gov[.]uk/guidance/business-communications-sms-and-telephone-best-practice

    and the CISA also cautions against trusting shortened URLS:

    https://www.cisa[.]gov/secure-our-world/recognize-and-report-phishing

    Curious why you would encourage people to use shortened URLs when they are an obvious security risk and their use contravenes the guidance of both the NCSC and the CISA? Not to mention they’ve been leveraged in recent ConsentFix and ClickFix campaigns, as well as phishing campaigns on LinkedIN.

    Love you devs, but if you would start being responsible security partners, that would be great.

    1. Alex Neihaus Avatar
      Alex Neihaus

      First, thanks for commenting.

      You’re not wrong, of course. It’s true that phishing attacks — most of which originate from emailed links — are today’s biggest threat.

      And it’s also true that it’s pushing a rock uphill trying to teach people to inspect links in email before opening them.

      But people are gonna open links in email. So, in the absence of something like Microsoft 365 Safe Links, I think it can be valuable to teach people to trust a personal link shortener.

      The instruction goes something like this: “Do not open any links in email…unless you see this personal link shortener domain address.” IOW, you train users to only open links from a curated list.

      Safe Links tries to curate on an enterprise scale — all links for all users in all mailboxes.

      I use shlink to curate for friends and family who trust me. They know that if they get a message from me containing a link that’s from my shlink domain, they can trust it.

Leave a Reply

Your email address will not be published. Required fields are marked *