Self-hosted alternatives to Bitly and Rebrandly: Shlink and YOURLS compared

Shlink and YOURLS on one Debian 13 server, each with a branded short domain, the same 200 links with custom slugs and UTM parameters, and 10,000 simulated clicks, compared on statistics, bots, privacy, QR codes, expiry, users, Bitly import, speed, memory and cost, next to Bitly and Rebrandly.

Tested on Shlink 5.1.7 and YOURLS 1.10.6 on Debian 13 (trixie) on a Melonslab server Updated October 14, 2026

Recommended server for this guide

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy · 2 vCPU · 8 GB Memory · 60 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€8.99/mo

Deploy now
On this page

The short answer

Pick Shlink for a marketing team. It has tags, expiry dates, a maximum number of visits, QR codes, redirects by device or language, and statistics by country, city, browser and referrer, with bots kept apart. It stores visitors' IP addresses shortened by default. With Shlink Dashboard, colleagues get their own logins, and the API key never reaches their browsers. It is the slower of the two, at about 60 ms per redirect against 13, and the Dashboard is still before version 1.0.

Pick YOURLS if one or two people need a branded short domain and little else. It was the fastest and simplest in our test. But it has no tags, expiry or QR codes without plugins, it counts bots as clicks, it stores every visitor's full IP address, and every user can do everything.

Both are maintained and MIT-licensed. Neither lets strangers create links in the setup below.

What we tested

We installed both on the same fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13, each under a user of its own, as rootless Podman Quadlet units behind the same Caddy, with passwords and keys as Podman secrets. Shlink used PostgreSQL 18 and YOURLS MariaDB 12.3, the database each supports best. We have no guide for either yet, so How we ran Shlink and How we ran YOURLS have the unit files, on top of the Podman guide.

Both are maintained. Shlink 5.1.7 was released on 21 September 2026, and YOURLS 1.10.6 on 30 August 2026, according to their GitHub release pages, which we read on 14 October 2026. So we did not need a substitute. Of the usual alternatives, Kutt is alive too, with version 3.2.6 on 5 July 2026; we did not test it. Dub publishes its code under the AGPL 3.0, but its self-hosting guide needs accounts with Vercel, Tinybird, Upstash and PlanetScale, so it does not run on your own server alone.

Each app got its own short domain on a subdomain of our test domain, go.example.com in this page. Then we did the same things in both:

  • created 200 links through the API, each with a custom slug such as spring-sale-linkedin-000 and UTM parameters in the destination, for 8 channels and 10 products. In Shlink, each link also got two tags, 20 links an end date and 20 links a maximum of 500 visits;
  • tried to create links without logging in, through the API, the admin pages and the installer;
  • sent 10,000 simulated clicks to each, eight at a time, from a script on the server itself. These were not real visitors. The script gave each click a made-up visitor address from Swedish, Norwegian, Finnish, Danish, German, British, French and US networks, about 10% of them IPv6. It also gave each click a browser on Windows, macOS, iPhone or Android, and a referrer: LinkedIn, Instagram, Facebook, Google or none. About 13% of the clicks used bot user agents, such as Googlebot, LinkedInBot, facebookexternalhit, curl and python-requests. A few links got most of the clicks, as in real life;
  • then checked what each showed, measured redirect times through Caddy, memory at rest after a reboot and at the peak of the clicks, disk growth, and the time to install;
  • made QR codes, set up a second user, imported links from a CSV file, rebooted the server, and made and restored a database backup of each.

Bitly and Rebrandly themselves were not tested. We did not sign up for either. What this page says about them comes from their own pricing, help and API pages, linked where we use them and read on 14 October 2026.

The numbers

Shlink 5.1.7YOURLS 1.10.6
Containers2: Shlink, PostgreSQL 18; 3 with Shlink Dashboard 0.3.12: YOURLS, MariaDB 12.3
Memory at rest after a reboot85 MB (Shlink 79); 134 MB with the Dashboard (49)142 MB (MariaDB 130, YOURLS 12)
Memory at the peak of 10,000 clicksabout 110 MB (Shlink 99)about 167 MB (MariaDB 137, YOURLS 30)
Images on disk0.62 GB; 1.20 GB with the Dashboard0.89 GB
Location databaseDB-IP's free city database, 127 MB, downloadedMaxMind GeoLite2 Country, 8.6 MB, included
Database volume after the test82 MB179 MB
Disk used per 10,000 clicks7.1 MB10.4 MB (3.1 MB of it in the click table)
From useradd to a working app46 seconds68 seconds, with the installer
200 links through the API15 seconds5 seconds
10,000 clicks, eight at a time326 seconds, 31 a second39 seconds, 256 a second
Redirect time through Caddy, median (95th percentile)61 ms (79 ms)13 ms (20 ms)
Redirect302; 301, 307 or 308 if you choose301
Tags, end date, maximum visitsyesno, only with plugins
QR codesyes, in the web client and Dashboardno, only with plugins
Location in statisticscountry, region, citycountry
Bots kept apartyes, all 1,305 bot clicks flaggedno, counted as clicks
Visitor IP address storedIPv4 with the last part set to 0, IPv6 not at allin full
Users and permissionsAPI keys with roles; users per server in the Dashboardusers in a config file, all with full rights
Import from Bitlybuilt in, needs a Bitly token (not tested)no, only with third-party scripts
Swedish interfacenono
LicenceMITMIT

Memory is the sum of each app's containers, measured with podman stats four to five minutes after a reboot. The peak is the highest reading while the 10,000 clicks ran. The setup time is how long the commands took to run, mostly downloading images, not the time you need to read and type them. Redirect times were measured with 300 requests in a row over one HTTPS connection to Caddy, from the server itself, so they leave out the network between a visitor and the server.

Keep strangers out

A link shortener that anyone can use is soon found by spammers and phishers. They hide their destinations behind your trusted domain. Then the lists that browsers, email filters and social networks use, such as Google Safe Browsing and Spamhaus's domain blocklist, add your short domain. After that, your own links get a red warning page or are dropped from messages. Getting off a list takes time, and a listing can reach further than the short links themselves. Bitly checks for this itself; its help pages say that "Bitly blocks a link when it's flagged as suspect". On your own server, the only defence is that nobody can create links without logging in.

In the setup below, neither app lets strangers in, and we checked:

  • Shlink has no login page at all; everything goes through its API, which needs a key. Creating a link without a key gave 401 with "Expected one of the following authentication headers, "X-Api-Key"". A wrong key gave 401 "Invalid API key" too. The web client and the Dashboard are only interfaces to that API. The Dashboard starts with the user admin and the password admin, so change it over an SSH tunnel before Caddy makes it public, as in How we ran Shlink.
  • YOURLS is "private" by default, as long as you do not set YOURLS_PRIVATE=false. Its API answered "Please log in" with error code 403, and its admin pages, its add-link call and its statistics pages showed the login form. Its installer runs for anyone until it has been run once, so run it before Caddy makes YOURLS public, as we did. Afterwards it answered "YOURLS already installed."

Setup

Neither app has an official Podman or Quadlet setup; both publish container images and Compose examples, which we turned into Quadlet units.

  • Shlink is one image with its API and redirects. It creates its database tables at start and its first API key from a Podman secret, so it was ready 46 seconds after we created its user, with no setup in a browser. The web interface is a separate app. Shlink 5 has no QR codes of its own: they moved to the web client and the Dashboard in version 5.0. Without a MaxMind licence key, Shlink does not download a location database. We gave it DB-IP's free city database in the same format instead, which it used without complaint.
  • YOURLS is one image with PHP and Apache. Its installer is a single button, which we pressed with curl from the server before Caddy was in front. Two things were needed before it worked well behind Caddy:
    • A small plugin to trust Caddy. Without it, YOURLS logged every click with the server's own address, and with the country that address belongs to. YOURLS only reads X-Forwarded-For from addresses a plugin lists. How we ran YOURLS has the plugin, in five lines.
    • The bundled plugin "Allow Hyphens in Short URLs". Without it, YOURLS quietly removed the hyphens from our custom slugs: spring-sale-linkedin-000 became springsalelinkedin000. The API still said success and returned the changed address. We only noticed when all 10,000 of our first clicks ended up on YOURLS's front page, which is where it sends unknown slugs. We switched the plugin on and created the links again.

Both took our 200 links through their API with the custom slugs and the UTM parameters intact. Both also have a form for single links in their web interfaces.

  • Shlink added two tags to each link and accepted an end date (validUntil) and a maximum number of visits (maxVisits). A link that ended 20 seconds after we created it answered 302 before and 404 after. A link limited to 3 visits answered 404 on the fourth. You can send those visitors to a page of your own with DEFAULT_INVALID_SHORT_URL_REDIRECT. The Dashboard's form has fields for both, under Limit access to the short URL. Shlink also passes query parameters on the short link to the destination, so go.example.com/fair?utm_source=poster adds utm_source to the long address. It can redirect by device, language, query parameter, country or date too. A rule we set sent an iPhone to the App Store and a browser asking for Swedish to the Swedish page.
  • YOURLS stores a slug, a destination and a title, and nothing more. Tags, end dates and click limits need plugins, such as Expiry, listed on Awesome YOURLS. We did not test any of them. By default it allows one short link per destination; according to its configuration file, YOURLS_UNIQUE_URLS=false lets you make several, one for each channel.

QR codes. In Shlink's web client and Dashboard, every link has a QR code item in its menu, with size, margin, colours, error correction, a logo and PNG or SVG. The code is made in your browser. We downloaded one, and a QR reader read the right short address from it. YOURLS has no QR codes. The official example plugin sends the short address to the outside service api.qrserver.com, which draws the code. The "local" plugin we looked at still loaded two scripts from other sites, so we did not install it. For YOURLS, make QR codes with any QR tool you trust.

301 or 302

The redirect code decides who remembers the destination.

  • A 301 means "moved permanently". Browsers may remember it and skip the short link next time, so those visits are not counted, and if you change the destination, people who have been there before may still go to the old one. Search engines read a 301 as a lasting move to the destination.
  • A 302, or a 307, means "for now". The browser asks the short link every time, so every visit is counted and a changed destination takes effect at once.

For short links shared on social media, in print and in QR codes, search ranking hardly matters, since the links are rarely indexed: Shlink's robots.txt asks search engines to stay away, and YOURLS sends X-Robots-Tag: noindex. Statistics and the freedom to change a destination matter more, and a printed QR code can only be fixed by changing where it points.

  • Shlink answers 302 by default. REDIRECT_STATUS_CODE switches it to 301, 307 or 308, and with a 301, according to its documentation, Shlink tells browsers to remember it for only 30 seconds (REDIRECT_CACHE_LIFETIME), so most visits are still counted. We tested the default.
  • YOURLS answers 301, with no instruction on how long to remember it, and only a plugin can change that.
  • Bitly and Rebrandly both use 301, according to Bitly's help pages, "we issue a 301 redirect", and Rebrandly's, "we use the 301 redirect".

Statistics from 10,000 simulated clicks

Both counted all 10,000 clicks. What they showed differed.

Shlink keeps every visit with its time, referrer, user agent, country, region and city, and whether it looks like a bot. The web client and the Dashboard show, per link, tag or for everything:

  • visits over time;
  • operating systems, browsers and referrers;
  • countries and cities;
  • a list of single visits.

You can export any of it as CSV. Our most-clicked link showed Windows, macOS, Android and iOS, Chrome and Safari, and the referrers by host. Its cities included Malmö, Gothenburg, Stockholm, Oslo, London and Berlin. Shlink also records visits to its front page and to slugs that do not exist, as "orphan visits", which showed us scanners from the internet within minutes of going online.

YOURLS shows, per link, for the last 24 hours, 7 days, 30 days and all time:

  • clicks over time;
  • the top countries, on a map;
  • direct visits against referred ones;
  • the list of referring sites.

That list showed each of our four referrers with "1" next to it, not their 481, 256, 230 and 178 clicks, which were only in the database. YOURLS stores the user agent but shows nothing about devices or browsers. The times followed UTC until you set YOURLS_HOURS_OFFSET, which is a fixed number of hours and does not follow summer time. YOURLS's statistics pages also draw their charts with Google Charts, loaded from google.com, and its link list shows each destination's icon through Google. So the browsers of the people using the admin pages contact Google; the visitors who click your links do not.

Locations. Both look up the visitor's address in a database file on the server; no address is sent anywhere. YOURLS includes MaxMind's GeoLite2 Country, built on 21 August 2026, under MaxMind's GeoLite licence. It is updated when YOURLS releases a new version, or by you with a MaxMind account. Shlink downloads MaxMind's GeoLite2 City if you give it a licence key from a free MaxMind account. We did not create one, and used DB-IP's free city database instead. It is under the Creative Commons Attribution 4.0 licence, which asks you to credit DB-IP where you show its data, and DB-IP publishes a new one every month. The two databases disagreed on part of our made-up traffic. GeoLite placed our IPv6 addresses in Sweden, and DB-IP placed them in the UK, so Sweden had 5,348 clicks in YOURLS and 3,894 in Shlink. Treat the country of a single visitor as a good guess.

For statistics of your website itself, beyond the click on the link, the UTM parameters end up in your web analytics; see Plausible or Umami or Matomo. Shlink can also send its visits to Matomo (MATOMO_ENABLED), which we did not test.

Bots

Our 10,000 clicks included 1,305 with bot user agents. These are what link previews in LinkedIn, Facebook and Slack, search engines and scripts look like.

  • Shlink flagged all 1,305 as "potential bot", and the API reported 9,353 visits from people and 1,308 from bots, counting three of our own test clicks with curl. The web client shows bots by default; Exclude bots wherever possible under Settings > Visits hides them.
  • YOURLS counted every bot as a click. On a link that is shared a lot, link previews alone make its numbers look better than they are.

Neither can tell a person from a script that pretends to be a browser. Bitly says the same of itself: it "filters bot traffic to the best of our ability" but is "not designed to detect or counteract click fraud", according to its help pages.

Privacy and GDPR

A click on a short link is personal data as soon as it includes an IP address. Here the two differ the most.

  • Shlink shortens IPv4 addresses before it stores them by default (ANONYMIZE_REMOTE_ADDR): a visitor from 81.228.102.134 was stored as 81.228.102.0. For IPv6 visitors it stored no address at all, while it still looked up their location first. Its documentation warns that switching this off means your instance is "no longer be in compliance with the GDPR". You can also switch off tracking of addresses, referrers or user agents one by one, or all tracking.
  • YOURLS stored every visitor's full address: 81.228.102.134 was saved as 81.228.102.134, next to the time, the referrer and the user agent. It has no setting to shorten them. YOURLS_NOSTATS=true switches off click logging entirely, statistics included.

Neither deletes old visits by itself. Shlink has commands to delete visits per link and all orphan visits; for YOURLS you delete rows in the database. On your own server in Sweden, the data does not leave the EU, which neither Bitly nor Rebrandly can say. Bitly's help pages say "Our data centers are in the United States", and Rebrandly's data processing addendum says its "primary processing facilities are in the United States of America".

A web interface for colleagues

Shlink's own server has no web interface. There are three ways to get one:

  • Shlink Dashboard, the newer one, is a small Node.js app you run next to Shlink, with its own users. We gave our colleague Erik a login: he signed in, changed his temporary password, and created a link with a custom slug in the form. The browser only talked to the Dashboard, and Shlink's API key was not in anything it received: the Dashboard keeps it on the server. It is at version 0.3.1, so expect changes.
  • The Shlink web client is a static app, a single small container we ran under a name of its own. Each person adds the server with its address and an API key, which the browser keeps in its local storage, in plain text. It contacted nothing but our own two addresses.
  • app.shlink.io is the same web client, published by Shlink. The app's code comes from shlink.io, but it runs in your browser and talks to your Shlink directly, so your links and statistics do not pass through shlink.io. Its API key is stored in the browser, as with the self-hosted copy. It works because Shlink allows calls from any website by default (CORS_ALLOW_ORIGIN). We did not use it. The cost is trust: each time you open it, you run code from someone else's server with your API key, and a computer that is lost or shared hands over that key. For a company we would run the Dashboard.

The web client and the Dashboard are in English only. YOURLS can load translations, but Awesome YOURLS lists none in Swedish.

YOURLS has its admin pages built in: a form at the top for a link and an optional slug, a list with search, and a Stats page per link. Anna created a link there in a few seconds. It looks dated, but it is easy for anyone.

Users and permissions

  • Shlink has API keys, not users. A key can be limited: --author-only gives a key that only sees and changes its own links. Erik's key listed his one link out of 208, and answered "Short URL not found" when it tried to read or delete one of Anna's. Keys can also be limited to one domain. In the Dashboard, an admin creates users, as managed user, advanced user or admin, and decides which servers each managed user may use. A server entry has a single API key, so Erik saw and could change everything the key allowed, all links in our test. To limit someone, give their server entry an --author-only key.
  • YOURLS reads its users from config.php: one from the container's environment, more as lines in the file. We added Erik that way, and his login worked in the admin pages and the API. Every user can see, change and delete every link; there are no roles. LDAP and other logins exist as third-party plugins, which we did not test.

Import from Bitly

  • Shlink has an importer: shlink short-url:import bitly. It asks for a Bitly access token, and whether to keep the short codes, tags, custom domains and creation dates. We had no Bitly account, so we did not test the Bitly import; we only saw it ask. Shlink's documentation lists imports from Bitly, YOURLS, Kutt.it, another Shlink and CSV. We tested the CSV import with a file we wrote ourselves, with the columns Long URL, Short code, Title and Tags. All three links came in with their slugs and tags and redirected. Bitly's own CSV export uses other column names, such as "Destination URL", according to its help page, so you would rename them first.
  • YOURLS has no importer. Awesome YOURLS lists BitlyEscape, scripts that read your Bitly links with a token for the YOURLS Import Export plugin. We did not test them.

What moves are your own links. Links on bit.ly itself stay with Bitly. Links on your own domain keep working when you point the domain at your server, as long as the slugs are the same, which is what Shlink's importer is for. Click history does not come along with any of the methods.

Resources and speed

Both are small next to an 8 GB server: about 85 and 142 MB at rest, and under 170 MB at their peaks.

YOURLS was much faster. It answered 256 clicks a second where Shlink answered 31, on the same two cores. A single redirect took 13 ms against 61 ms through Caddy. With tracking switched off, Shlink still took 42 ms, so most of the time is in Shlink itself, not in storing the visit or looking up the location. For a visitor on a phone, 60 ms is still small next to the network; but at a sudden peak of thousands of clicks a minute, YOURLS has much more room. On disk, Shlink's database grew 7.1 MB for the 10,000 clicks and YOURLS's 10.4 MB, so a year of 100,000 clicks a month takes under 1.5 GB in either.

Updates and backups

Both units pin the version we tested; to update, change the tag after reading the release notes. Shlink has had four patch releases since June, and YOURLS three. YOURLS's image copies its program files into the container at every start and keeps only your user folder, with the settings and plugins, in a volume. So a new image brings a new YOURLS. The databases follow their own tags with AutoUpdate=registry, as in our other guides.

A backup is a database dump plus the Podman secrets. For YOURLS add the yourls-user volume, which holds config.php with its cookie key, your extra users and your plugins. For Shlink, add the location database or download it again. Our dumps were 378 KB for Shlink and 176 KB for YOURLS, compressed. We restored Shlink's into an empty database and YOURLS's over its own, and counted the same links and clicks. Both came back by themselves after a reboot.

Shlink's documentation runs its image with environment variables; we ran it with PostgreSQL in a pod, with the Dashboard next to it. As a user shlink, created like any app user in the Podman guide, create the secrets:

openssl rand -hex 24 | tr -d '\n' | podman secret create shlink-db-password -
openssl rand -hex 24 | tr -d '\n' | podman secret create shlink-api-key -
openssl rand -hex 32 | tr -d '\n' | podman secret create shlink-dashboard-session -

Download this month's location database from DB-IP:

mkdir -p ~/geoip
curl -fsSL https://download.db-ip.com/free/dbip-city-lite-$(date +%Y-%m).mmdb.gz | gunzip > ~/geoip/dbip-city-lite.mmdb

~/.config/containers/systemd/shlink.pod:

[Pod]
PodName=shlink
PublishPort=127.0.0.1:8158:8080
PublishPort=127.0.0.1:8163:3005

[Install]
WantedBy=default.target

shlink-db.container:

[Container]
ContainerName=shlink-db
Image=docker.io/library/postgres:18-alpine
Pod=shlink.pod
Volume=shlink-db:/var/lib/postgresql
Environment=POSTGRES_USER=shlink POSTGRES_DB=shlink
Secret=shlink-db-password,type=env,target=POSTGRES_PASSWORD
HealthCmd=pg_isready -U shlink
HealthInterval=10s
Notify=healthy
AutoUpdate=registry

[Service]
Restart=always
TimeoutStartSec=300

shlink-app.container:

[Unit]
Requires=shlink-db.service
After=shlink-db.service

[Container]
ContainerName=shlink-app
Image=docker.io/shlinkio/shlink:5.1.7
Pod=shlink.pod
Environment=DEFAULT_DOMAIN=go.example.com IS_HTTPS_ENABLED=true TIMEZONE=Europe/Stockholm
Environment=DB_DRIVER=postgres DB_HOST=127.0.0.1 DB_USER=shlink DB_NAME=shlink
Secret=shlink-db-password,type=env,target=DB_PASSWORD
Secret=shlink-api-key,type=env,target=INITIAL_API_KEY
Volume=%h/geoip/dbip-city-lite.mmdb:/etc/shlink/data/GeoLite2-City.mmdb:ro,Z

[Service]
Restart=always
TimeoutStartSec=300

shlink-dashboard.container:

[Unit]
Requires=shlink-db.service
After=shlink-db.service

[Container]
ContainerName=shlink-dashboard
Image=docker.io/shlinkio/shlink-dashboard:0.3.1
Pod=shlink.pod
Environment=SHLINK_DASHBOARD_DB_DRIVER=postgres SHLINK_DASHBOARD_DB_HOST=127.0.0.1 SHLINK_DASHBOARD_DB_USER=shlink
Secret=shlink-db-password,type=env,target=SHLINK_DASHBOARD_DB_PASSWORD
Secret=shlink-dashboard-session,type=env,target=SHLINK_DASHBOARD_SESSION_SECRETS

[Service]
Restart=always
TimeoutStartSec=300

The Dashboard needs a database of its own, shlink_dashboard. Start PostgreSQL first, create it, then start the rest:

systemctl --user daemon-reload
systemctl --user start shlink-db
podman exec shlink-db createdb -U shlink shlink_dashboard
systemctl --user start shlink-pod

Shlink is ready when curl -s http://127.0.0.1:8158/rest/health prints "status":"pass". Shlink reads the visitor's address from X-Forwarded-For, which Caddy sets, without further settings. Before you add the Dashboard to Caddy, open an SSH tunnel from your computer, ssh -L 8163:127.0.0.1:8163 root@203.0.113.10, go to http://localhost:8163, log in as admin with the password admin, and set a new one. Then choose Add a server, with the URL http://127.0.0.1:8080, which reaches Shlink inside the pod without going through Caddy, and the API key from:

podman secret inspect --showsecret shlink-api-key --format '{{.SecretData}}'

Under Manage users, create your colleagues as managed user; each gets a temporary password to change at the first login. Choose Servers in a user's menu to give them the server. For a key that only sees its own links:

podman exec shlink-app shlink api-key:generate --name erik --author-only

As caddy, add both names to ~/Caddyfile and restart Caddy:

go.example.com {
    reverse_proxy 127.0.0.1:8158
}

links.example.com {
    reverse_proxy 127.0.0.1:8163
}

If you would rather use the static web client, run docker.io/shlinkio/shlink-web-client:4.8.1 as a container of its own with PublishPort=127.0.0.1:8162:8080, under a name of its own in Caddy. DB-IP publishes a new database at the start of every month: run the curl line again and systemctl --user restart shlink-app. Shlink sends visitors to the front page to a 404 page; DEFAULT_BASE_URL_REDIRECT=https://www.example.com sends them to your website instead.

How we ran YOURLS

YOURLS's image documentation runs it with MySQL; we ran it with MariaDB in a pod, with both passwords read from files. As a user yourls:

openssl rand -hex 24 | tr -d '\n' | podman secret create yourls-db-password -
openssl rand -base64 18 | tr -d '\n' | podman secret create yourls-admin-password -

~/.config/containers/systemd/yourls.pod:

[Pod]
PodName=yourls
PublishPort=127.0.0.1:8159:8080

[Install]
WantedBy=default.target

yourls-db.container:

[Container]
ContainerName=yourls-db
Image=docker.io/library/mariadb:12.3
Pod=yourls.pod
Volume=yourls-db:/var/lib/mysql
Environment=MARIADB_DATABASE=yourls MARIADB_USER=yourls MARIADB_RANDOM_ROOT_PASSWORD=1 MARIADB_AUTO_UPGRADE=1
Secret=yourls-db-password,type=env,target=MARIADB_PASSWORD
HealthCmd=healthcheck.sh --connect --innodb_initialized
HealthInterval=10s
Notify=healthy
AutoUpdate=registry

[Service]
Restart=always
TimeoutStartSec=300

yourls-app.container:

[Unit]
Requires=yourls-db.service
After=yourls-db.service

[Container]
ContainerName=yourls-app
Image=docker.io/library/yourls:1.10.6-apache
Pod=yourls.pod
Volume=yourls-user:/var/www/html/user
Environment=YOURLS_SITE=https://go.example.com YOURLS_USER=anna
Environment=YOURLS_DB_HOST=127.0.0.1 YOURLS_DB_USER=yourls YOURLS_DB_NAME=yourls
Secret=yourls-db-password,target=/run/secrets/db-password
Secret=yourls-admin-password,target=/run/secrets/admin-password
Environment=YOURLS_DB_PASS_FILE=/run/secrets/db-password YOURLS_PASS_FILE=/run/secrets/admin-password

[Service]
Restart=always
TimeoutStartSec=300

Start it with systemctl --user daemon-reload and systemctl --user start yourls-pod. On the first start, MariaDB takes about 20 seconds to set itself up before YOURLS starts. Before Caddy makes it public, run the installer and add the plugin that trusts Caddy, with your server's IPv4 address in place of 203.0.113.10:

curl -s -X POST -d 'install=Install+YOURLS' http://127.0.0.1:8159/admin/install.php | grep -o 'successfully created'
podman exec -u www-data yourls-app mkdir -p user/plugins/trust-caddy
podman exec -i -u www-data yourls-app tee user/plugins/trust-caddy/plugin.php > /dev/null <<'EOF'
<?php
/*
Plugin Name: Trust Caddy
Description: Take the visitor address from X-Forwarded-For when the request comes from Caddy
Version: 1.0
*/
yourls_add_filter( 'get_ip_trusted_proxies', fn() => [ '203.0.113.10' ] );
EOF

As caddy, add the site and restart Caddy:

go.example.com {
    reverse_proxy 127.0.0.1:8159
}

Log in at https://go.example.com/admin/ as anna, with the password from podman secret inspect --showsecret yourls-admin-password --format '{{.SecretData}}'. Under Manage Plugins, activate Trust Caddy and Allow Hyphens in Short URLs. For more users, add a line per person to config.php in the volume, such as $yourls_user_passwords['erik'] = 'a-long-password';, with podman exec -it -u www-data yourls-app vi user/config.php. We used the password with the API; the Tools page also has a signature token for it, which we did not try.

Licence and cost

We read the licences and the projects' pages on 14 October 2026.

  • Shlink is open source under the MIT licence, made by Alejandro Celaya with contributors, and so are its web client and Shlink Dashboard. There is no paid edition and no paid feature; the project asks for donations. The location database is free from DB-IP, or from MaxMind with a free account.
  • YOURLS is open source under the MIT licence, made by its community, with no company and no paid edition. Its bundled location database is under MaxMind's GeoLite licence. Plugins come from many authors under their own licences.

For a company running its own short links, both cost only the server.

Bitly and Rebrandly today

What follows is from Bitly's and Rebrandly's own pages, read on 14 October 2026, not from our tests.

Bitly's pricing page listed these plans, in US dollars:

  • Free, "With Ads": 5 links and 2 QR codes a month, no custom domain, and no click history;
  • Core: 10 dollars a month, "Billed annually only", with 100 links a month and 30 days of click history;
  • Growth: 29 dollars a month billed yearly, or 35 monthly, with 500 links, a "Complimentary custom domain", branded links and 120 days of history;
  • Premium: 199 dollars billed yearly, or 300 monthly, with 3,000 links, one year of history, and city and device data;
  • Enterprise: a price you ask for. It is the only plan the page lists with "Multiple users & group permission management", and it keeps two years of history.

The page lists CSV export of data from Growth up. Bitly's help pages say free accounts now get "an interstitial page that includes a preview of the link destination and an ad". On expiry: "Bitly Links never expire", unless you set an end date, which "redirects to either a Bitly 410 page or your custom error page" on some paid plans. After a downgrade, "Your bit.ly links continue to work", but on a custom domain Bitly "won't renew your domain's SSL certificate", according to its help page. Bitly's data centres are in the United States.

Rebrandly's pricing page listed:

  • Free: 10 links and 10 QR codes a month, with a Rebrandly watermark on the codes, and click details for 100 clicks a month;
  • Essentials, Professional and Growth, at prices from about 8 US dollars a month for Essentials to about 104 to 149 for Growth. The page showed two prices per plan, and depending on switches for yearly billing and "High Volume Plans" we could not read reliably, so check before you buy;
  • Enterprise: a price you ask for.

Teammates start with Professional, and link expiry, city and referrer data too. Rebrandly says it "automatically removes all bot/spider traffic from your click counter" and that "Even if you downgrade to a free account, all your links will continue to function", according to its help pages. Its importer cannot bring "Generic links that begin with a domain you do not own, like bit.ly" or click history. Its data is hosted with Amazon Web Services in the eastern United States, according to its list of subprocessors.

We found no setting on either site to switch from 301 to 302, and no choice to keep the data in the EU.

What you give up compared with Bitly

With your own server, the links stay in Sweden, the statistics are kept as long as you like, and there are no ads, link quotas or per-user prices. But be honest about what you leave behind:

  • Somebody watching for abuse. Bitly blocks links flagged as suspect, and Rebrandly checks links for "spam, malware, or illegal content", according to their help pages. On your own server, the logins are your only defence, so keep them few and strong.
  • Uptime is yours. If your server is down, every short link and every printed QR code is broken until it is back. Plan for monitoring, such as Uptime Kuma, and backups.
  • Bots filtered for you. Shlink flags them, but YOURLS does not, and neither is a click-fraud filter.
  • A polished app. Bitly's and Rebrandly's web apps are made for people who never think about servers. Shlink's Dashboard is young, the web client is for people who can handle an API key, and YOURLS looks its age.
  • Your old bit.ly links. Links on bit.ly cannot move; only those on your own domain can, and their click history stays behind.

Which one to pick

A marketing team with several people: Shlink with Shlink Dashboard. Tags per channel and product, end dates and visit limits for offers, QR codes with your colours, redirects by device or language, city, browser and referrer statistics with bots apart, shortened IP addresses, a CSV export, and logins for colleagues without sharing an API key. Its weak points are slower redirects than YOURLS, a Dashboard before version 1.0, permissions that come from API keys rather than real roles, no Swedish interface, and a location database you download yourself. The files are in How we ran Shlink, on top of the Podman guide.

One or two people who just want a branded short domain: YOURLS. Two containers, an admin page anyone understands, 13 ms redirects and a long list of plugins. Its weak points are full IP addresses in the database, bots counted as clicks, no tags, expiry or QR codes without plugins, every user with full rights, a slug quirk that removes hyphens unless you switch on a plugin, admin pages that load Google's scripts, and statistics in a fixed time zone. The files are in How we ran YOURLS.

Coming from Bitly with links on your own domain: Shlink. Its importer is made for that move, keeping slugs and tags; plan a test run with your Bitly token before you point the domain at your server.

Together they used about 330 MB at their peaks, with the Dashboard, so one VC-P Alloy runs either, or both, with plenty of room for other apps, such as Plausible or Umami for the website the links lead to.

Run it on your own server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy

€8.99/mo

vCPU
2
Memory
8 GB
Storage
60 GB
Transfer
10 TB
Performance
SSD · RAID 1
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides