Install and Use Shlink URL Shortener on Ubuntu / Debian

A URL Shortener is a tool used to create short URLs for long abbreviated web addresses. Other than shortening links by cleaning up the long links, the tools also enable you to measure the performance and effectiveness of your links. Long links can seem quite distracting and most of them may also appear like scams or phishing links that many people would prefer not to click on them. The tools are often used in internet marketing by digital marketers to share links with a lot of people.

The short links look more appealing and can be shared on social media sites that may have character limits like Twitter. You can also brand your links to create awareness of your product. They work in a way that when you click on the short link you are automatically redirected to the original long URL. There are quite a few services that offer shortening of URLs with the most popular ones being Bitly, Rebrandly, TinyURL, Hyperlink, and more.

Shlink abbreviation for ‘short link‘ URL Shortener is a tool that enables you to shorten long and suspicious URLs to short and visually appealing URLs. It is an open-source self-hosted URL shortener based on PHP that provides both a REST and a CLI interface to interact with it. It comes as software that can be installed on any distribution or can be deployed as a docker image. It also has a web client known as Shlink web client which is a web application that lets you manage multiple Shlink instances and see very detailed and customizable visits statistics.

Shlink also integrates with third-party applications like RabbitMQ to allow you to subscribe to events on Shlinkfor real-time updates. While it measures the visitation statistics, it can also detect visits made by potential bots. Shlink exposes a fully featured API that allows integration from anywhere but also limits the resources with which an API key can interact by using either the domain or the short URL created. You can also import your already existing short URLs from third parties like bit.ly or YOURLS.

The features of the Shlink URL shortener include;

  • URL Shortening
  • Track the performance of the short links.
  • Custom slugs to personalize your campaigns’ short URLs.
  • Serve multiple short domains under the same Shlink instance.
  • Limit access to short URLs, by date range and/or the maximum number of visits.
  • Tag your URLs to use in classification.
  • Use emojis on your URLs.
  • Generate QR codes pointing to your short URLs.
  • Email tracking by taking advantage of its URL tracking capabilities.

This guide will show you how to install and use Slink URL Shortener on Ubuntu|Debian.

Option 1: Install Shlink URL Shortener using a docker image

The docker image option is considered the simplest installation use case, since it includes all dependencies and works standalone. Start by installing Docker engine on the system.

curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Add your user account to docker group.

sudo usermod -aG docker $USER
newgrp docker

The docker images for Shlink are published both in Docker hub and Github Container Registry. In order to install images from the latter, use ghcr.io/shlinkio/shlink instead of shlinkio/shlink.

The most basic way to run Shlink’s docker image is by providing these mandatory env vars.

  • DEFAULT_DOMAIN: The custom short domain used for this Shlink instance. For example urls.example.com.
  • IS_HTTPS_ENABLED: Either true or false.
  • GEOLITE_LICENSE_KEY: Your GeoLite2 license key. Learn more about this.

Generate GeoLite2 license key

Shlink makes use of a GeoLite2 database, by MaxMind, to geolocate visits to your short URLs.

In order to generate the license key, follow these steps.

  • Create a MaxMind account for GeoLite2
  • Generate a license key. Its important that you save the value, as you will not be able to recover it afterwards.

To run Shlink on top of a local docker service, and using an internal SQLite database, do the following:

docker run -d \
    --name shlink \
    -p 8080:8080 \
    -e DEFAULT_DOMAIN=urls.example.com \
    -e IS_HTTPS_ENABLED=true \
    -e GEOLITE_LICENSE_KEY=<YOUR-GEOLite2-Key> \
    shlinkio/shlink:stable
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

List running containers to confirm its state:

$ docker ps

CONTAINER ID   IMAGE                    COMMAND                  CREATED         STATUS         PORTS                                       NAMES
03c49bb25fec   shlinkio/shlink:stable   "/bin/sh ./docker-en…"   5 minutes ago   Up 5 minutes   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   shlink

Generate first API key

Once the Shlink container is running, we can interact with the CLI entry point by running shlink with any of the supported commands. Generate required API key for this.

docker exec -it shlink shlink api-key:generate

Example output:

 [OK] Generated API key: "835f9dd7-3f09-4b5c-bb5b-a1e769ca0da2" 

Interact with Shlink’s CLI on a running container.

See example on how you can list all tags:

$ docker exec -it shlink shlink tag:list
+---------------+-------------+---------------+
| Name          | URLs amount | Visits amount |
+---------------+-------------+---------------+
| No tags found | -           | -             |
+---------------+-------------+---------------+

How to locate remaining visits:

$ docker exec -it shlink shlink visit:locate
 [INFO] GeoLite2 db file is up to date.

 [OK] Finished locating visits

You can also list all available commands by just running this:

$ docker exec -it shlink shlink
Shlink 4.4.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display help for the given command. When no command is given display help for the list command
      --silent          Do not output any message
  -q, --quiet           Only errors are displayed. All other output is suppressed
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  completion                      Dump the shell completion script
  help                            Display help for a command
  list                            List commands
 api-key
  api-key:disable                 Disables an API key by name or plain-text key (providing a plain-text key is DEPRECATED)
  api-key:generate                Generate a new valid API key.
  api-key:list                    Lists all the available API keys.
  api-key:rename                  Renames an API key by name
 domain
  domain:list                     List all domains that have been ever used for some short URL
  domain:redirects                Set specific "not found" redirects for individual domains.
  domain:visits                   Returns the list of visits for provided domain.
 integration
  integration:matomo:send-visits  [MATOMO INTEGRATION DISABLED] Send existing visits to the configured matomo instance
 short-url
  short-url:create                Generates a short URL for provided long URL and returns it
  short-url:delete                Deletes a short URL
  short-url:delete-expired        Deletes all short URLs that are considered expired, because they have a validUntil date in the past
  short-url:edit                  Edit an existing short URL
  short-url:import                Allows to import short URLs from third party sources
  short-url:list                  List all short URLs
  short-url:manage-rules          Set redirect rules for a short URL
  short-url:parse                 Returns the long URL behind a short code
  short-url:visits                Returns the detailed visits information for provided short code
  short-url:visits-delete         Deletes visits from a short URL
 tag
  tag:delete                      Deletes one or more tags.
  tag:list                        Lists existing tags.
  tag:rename                      Renames one existing tag.
  tag:visits                      Returns the list of visits for provided tag.
 visit
  visit:download-db               Checks if the GeoLite2 db file is too old or it does not exist, and tries to download an up-to-date copy if so.
  visit:locate                    Resolves visits origin locations. It implicitly downloads/updates the GeoLite2 db file if needed.
  visit:non-orphan                Returns the list of non-orphan visits.
  visit:orphan                    Returns the list of orphan visits.
  visit:orphan-delete             Deletes all orphan visits

Option 2: Install Shlink URL Shortener manually (not recommended)

Follow steps given in this section.

1: Install LEMP server

Update your system packages to the latest version

sudo apt update && sudo apt upgrade -y

Install the LEMP server on your system. Shlink requires PHP, a web server (Apache, Nginx), and a Database (MySQL, MariaDB). For this guide, I will use Nginx as the web server and MariaDB as the Database server.

a) Install Nginx Web Server

Install Nginx with the following command

sudo apt install nginx -y

Start and enable the Nginx service.

sudo systemctl enable --now nginx

Check for its status with the following command.

$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-01-20 10:20:13 UTC; 16s ago
       Docs: man:nginx(8)
   Main PID: 13331 (nginx)
      Tasks: 4 (limit: 7025)
     Memory: 3.0M (peak: 3.2M)
        CPU: 19ms
     CGroup: /system.slice/nginx.service
             ├─13331 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             ├─13332 "nginx: worker process"
             ├─13333 "nginx: worker process"
             └─13334 "nginx: worker process"

Jan 20 10:20:13 ubuntu-noble systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Jan 20 10:20:13 ubuntu-noble systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.

b) Install MariaDB Server

Install MariaDB with the following command.

sudo apt install mariadb-server mariadb-client -y

The service is automatically started. Use the following command to check its status.

$ systemctl status mariadb
● mariadb.service - MariaDB 10.11.8 database server
     Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-01-20 10:21:29 UTC; 7s ago
       Docs: man:mariadbd(8)
             https://mariadb.com/kb/en/library/systemd/
   Main PID: 14529 (mariadbd)
     Status: "Taking your SQL requests now..."
      Tasks: 13 (limit: 46369)
     Memory: 78.8M (peak: 82.1M)
        CPU: 468ms
     CGroup: /system.slice/mariadb.service
             └─14529 /usr/sbin/mariadbd

Secure your Database instance by running the following post-installation script.

sudo mysql_secure_installation

Create a User for Shlink. First Log in to the MariaDB instance.

sudo mysql -u root

Create a database and user. Replace Pa55Word with a strong and secure password.

CREATE DATABASE shlink;
GRANT ALL ON shlink.* TO 'shlink'@'localhost' IDENTIFIED BY 'Pa55Word';
FLUSH PRIVILEGES;
EXIT;

c) Install PHP

We require PHP 8.1 or 8.2 and the required extensions. First, add a Sury repository to APT using the following command

On Ubuntu:

sudo add-apt-repository ppa:ondrej/php

On Debian

echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/sury-php.list
wget -qO - https://packages.sury.org/php/apt.gpg | apt-key add -

Use the following command to install PHP and its dependencies.

sudo apt update -y 
sudo apt install php8.2  php8.2-fpm php8.2-mysql php8.2-gd php8.2-common php8.2-curl php8.2-intl php8.2-gmp php8.2-xml php-dev php-pear unzip -y

Check for the version to verify the installation.

$ php -v
PHP 8.3.16 (cli) (built: Jan 19 2025 13:45:59) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.16, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.16, Copyright (c), by Zend Technologies

Restart Nginx to apply changes

sudo systemctl restart nginx

2: Download Shlink on Debian/Ubuntu

Download the latest version of Slink from the GitHub releases page. Alternatively use the wget utility as shown.

VER=$(curl -s https://api.github.com/repos/shlinkio/shlink/releases/latest|grep tag_name|cut -d '"' -f 4|sed 's/v//')
wget https://github.com/shlinkio/shlink/releases/download/v${VER}/shlink${VER}_php8.3_dist.zip

Extract the file contents.

unzip shlink${VER}_php8.3_dist.zip

Move the file to the /var/www/ directory

sudo mv shlink${VER}_php8.3_dist /var/www/html/shlink

Assign proper permissions to the directory.

sudo chown -R www-data:www-data /var/www/html/shlink/
sudo chmod -R 755 /var/www/html/shlink

3: Install Shlink on Ubuntu/Debian

Run the installation script to install Shlink.

sudo -u www-data php8.3 /var/www/html/shlink/vendor/bin/shlink-installer install

The setup asks for the database details including the type which is MariaDB, the database name is shlink, the database host localhost, the port is 3306, the username which is shlink, and the password.

root@ubuntu-noble:~# sudo -u www-data php8.3 /var/www/html/shlink/vendor/bin/shlink-installer install
 Welcome to Shlink!!
 This tool will guide you through the installation process.

SERVER
======

 Select the runtime you are planning to use to serve Shlink (this is only used to conditionally skip some follow-up questions) [RoadRunner]:
  [0] RoadRunner
  [1] Classic web server (Nginx, Apache, etc)
 > 1

 What is the maximum amount of RAM every process run by Shlink should be allowed to use? (Provide a number for bytes, a number followed by K for kilobytes, M for Megabytes or G for Gigabytes) [512M]:
 > 

DATABASE
========

 Select database type [MySQL]:
  [0] MySQL
  [1] MariaDB
  [2] PostgreSQL
  [3] MicrosoftSQL
  [4] SQLite [Not supported for production]
 > 1

 Database name [shlink]:
 > 

 Database host [localhost]:
 > 

 Database port [3306]:
 > 

 Database username:
 > shlink

 Database password:
 > Passw0rd

 Unix socket (leave empty to not use a socket):
 > 

 Do you want the database connection to be encrypted? Enabling this will make database connections fail if your database server does not support or enforce encryption. (yes/no) [no]:
 > 

Next, enter the default domain for your URL shortener and set the scheme type.

URL SHORTENER
=============

 Default domain for generated short URLs:
 > shlink.cloudspinx.com

 Is HTTPS enabled on this server? (yes/no) [yes]:
 > no

Follow through the other prompts to complete the setup as shown below. You can choose to go with the default settings by pressing Enter.


 What is the default length you want generated short codes to have? (You will still be able to override this on every created short URL) [5]:
 > 

 Do you want Shlink to resolve the short URL title based on the long URL's title tag (if any)? Otherwise, it will be kept empty unless explicitly provided. (yes/no) [yes]:
 > 

 Do you want Shlink to redirect short URLs as soon as the first segment of the path matches a short code, appending the rest to the long URL?
   * {shortDomain}/{shortCode}/[...extraPath] -> {longUrl}/[...extraPath]
   * https://example.com/abc123               -> https://www.twitter.com
   * https://example.com/abc123/shlinkio      -> https://www.twitter.com/shlinkio
    (yes/no) [no]:
 > 

 Do you want Shlink to redirect short URLs as soon as the first segment of the path matches a short code?

  append:
    * {shortDomain}/{shortCode}/[...extraPath] -> {longUrl}/[...extraPath]
    * https://s.test/abc123                    -> https://www.example.com
    * https://s.test/abc123/shlinkio           -> https://www.example.com/shlinkio

  ignore:
    * {shortDomain}/{shortCode}/[...extraPath] -> {longUrl}
    * https://s.test/abc123                    -> https://www.example.com
    * https://s.test/abc123/shlinkio           -> https://www.example.com

 [Match strictly]:
  [default] Match strictly
  [append ] Append extra path
  [ignore ] Discard extra path
 > 

 Do you want to support short URLs with multi-segment custom slugs? (for example, https://example.com/foo/bar) [Slugs and short codes will support only one segment (https://example.com/foo). Orphan visits will have one of "base_url", "invalid_short_url" or "regular_404" type.]:
  [yes] Custom slugs will support multiple segments (https://example.com/foo/bar/baz). Orphan visits will only have either "base_url" or "invalid_short_url" type.
  [no ] Slugs and short codes will support only one segment (https://example.com/foo). Orphan visits will have one of "base_url", "invalid_short_url" or "regular_404" type.
 > 

 Do you want to support trailing slashes in short URLs? (https://s.test/foo and https://s.test/foo/ will be considered the same) (yes/no) [no]:
 > 

 How do you want short URLs to be matched?
 Warning! This feature is experimental. It only applies to public routes (short URLs and QR codes). REST API routes always use strict match.
 [Short codes and custom slugs will be matched in a case-sensitive way ("foo" !== "FOO"). Generated short codes will include lowercase letters, uppercase letters and numbers.]:
  [strict] Short codes and custom slugs will be matched in a case-sensitive way ("foo" !== "FOO"). Generated short codes will include lowercase letters, uppercase letters and numbers.
  [loose ] Short codes and custom slugs will be matched in a case-insensitive way ("foo" === "FOO"). Generated short codes will include only lowercase letters and numbers.
 > 

 Provide a GeoLite2 license key. Leave empty to disable geolocation. (Go to https://shlink.io/documentation/geolite-license-key to know how to generate it):
 > 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

 What kind of redirect do you want your short URLs to have? [All visits will always be tracked. Not that good for SEO. Only GET requests will be redirected.]:
  [302] All visits will always be tracked. Not that good for SEO. Only GET requests will be redirected.
  [301] Best option for SEO. Redirect will be cached for a short period of time, making some visits not to be tracked. Only GET requests will be redirected.
  [307] Same as 302, but Shlink will also redirect on non-GET requests.
  [308] Same as 301, but Shlink will also redirect on non-GET requests.
 > 301

 How long (in seconds) do you want your redirects to be cached by visitors? [30]:
 > 

TRACKING
========

 Do you want to completely disable visits tracking? (yes/no) [no]:
 > 

 Do you want track orphan visits? (visits to the base URL, invalid short URLs or other "not found" URLs) (yes/no) [yes]:
 > 

 Provide a parameter name that you will be able to use to disable tracking on specific request to short URLs (leave empty and this feature won't be enabled):
 > 

 Provide a comma-separated list of IP addresses, CIDR blocks or wildcard addresses (1.2.*.*) from which you want tracking to be disabled:
 > 

 Do you want to disable tracking of visitors' IP addresses? (yes/no) [no]:
 > 

 Do you want visitors' remote IP addresses to be anonymized before persisting them to the database? (yes/no) [yes]:
 > 

 Do you want to disable tracking of visitors' "User Agents"? (yes/no) [no]:
 > 

 Do you want to disable tracking of visitors' "Referrers"? (yes/no) [no]:
 > 

REDIRECTS
=========

 Custom URL to redirect to when a user hits Shlink's base URL (If no value is provided, the user will see a default "404 not found" page):
 > 
         

 Custom URL to redirect to when a user hits an invalid short URL (If no value is provided, the user will see a default "404 not found" page):
 > 




 Custom URL to redirect to when a user hits a not found URL other than an invalid short URL (If no value is provided, the user will see a default "404 not found" page):
 > http://shlink.cloudspinx.com

QR CODES
========

 What's the default size, in pixels, you want generated QR codes to have (50 to 1000) [300]:
 > 

 What's the default margin, in pixels, you want generated QR codes to have [0]:
 > 

 What's the default format for generated QR codes [png]:
  [0] png
  [1] svg
 > 

 What's the default error correction for generated QR codes [Low]:
  [l] Low
  [m] Medium
  [q] Quartile
  [h] High
 > 

 Do you want the QR codes block size to be rounded by default? QR codes could end up having some extra margin, but it will improve readability [Round block size, improving readability]:
  [yes] Round block size, improving readability
  [no ] Do not round block size, preventing extra margin
 > 

 What's the default foreground color for generated QR codes [#000000]:
 > 

 What's the default background color for generated QR codes [#FFFFFF]:
 > 

 Provide a URL for a logo to be placed inside the QR code (leave empty to use no logo):
 > 

 Should Shlink be able to generate QR codes for short URLs which are not enabled? (Short URLs are not enabled if they have a "valid since" in the future, a "valid until" in the past, or reached the maximum amount of allowed visits) (yes/no) [yes]:
 > 

ROBOTS
======

 Do you want all short URLs to be crawlable/allowed by the robots.txt file? You can still allow them individually, regardless of this. (yes/no) [no]:
 > 

 Provide a comma-separated list of user agents for your robots.txt file. Defaults to all user agents (*):
 > 

APPLICATION
===========

 What is the amount of visits from which the system will not allow short URLs to be deleted? Leave empty to always allow deleting short URLs, no matter what:
 > 

 What is the path from which shlink is going to be served? (It must include a leading bar, like "/shlink". Leave empty if you plan to serve shlink from the root of the domain):
 > 

 Set the timezone in which your Shlink instance is running (leave empty to use the one set in PHP config):
 > 

 Prefix for cache entry keys. (Change this if you run multiple Shlink instances on this server, or they share the same redis instance) [Shlink]:
 > 

INTEGRATIONS
============

 Do you want to use a redis instance, redis cluster or redis sentinels as a shared cache for Shlink? (recommended if you run a cluster of Shlink instances) (yes/no) [no]:
 > yes

 Provide a comma-separated list of URIs (redis servers/sentinel instances). If they contains credentials with URL-reserved chars, make sure they are URL-encoded:
 > 

 Do you want Shlink to send all visits to an external Matomo server? (yes/no) [no]:
 > 

 Custom configuration properly generated!

Initializing database... Success!
Updating database... Success!
Generating proxies... Success!
Downloading GeoLite2 db file... Success!

                                                                                                                        
 [OK] Installation complete! 

4: Configure Nginx for Shlink

Create a virtual host file for Shlink.

sudo nano /etc/nginx/conf.d/shlink.conf

Add the following lines to the file.

server {
   listen 80;
   server_name shlink.cloudspinx.com;

   root /var/www/html/shlink/public;
   error_log /var/log/nginx/shlink.error;
   access_log /var/log/nginx/shlink.access;

   index index.php index.html index.htm index.nginx-debian.html;

   location / {
     # try to serve file directly, fallback to app.php
     try_files $uri /index.php$is_args$args;
   }

   # redirect some entire folders
     rewrite ^/(vendor|translations|build)/.* /index.php break;

   location ~ \.php$ {
     fastcgi_split_path_info ^(.+\.php)(/.+)$;
     fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
     fastcgi_index index.php;
     fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
     include fastcgi_params;
   }
}

Save and exit the file then check for any syntax errors.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Restart Nginx to apply changes

sudo systemctl restart nginx

5: Generate Short Links with Shlink

You can generate short links from the command line by using the following command

sudo -u www-data php8.3 /var/www/html/shlink/bin/cli short-url:generateTo list 

To list the short URLs:

sudo -u www-data php8.3 /var/www/html/shlink/bin/cli short-url:list

To display the help message, run the following command.

sudo -u www-data php8.3 /var/www/html/shlink/bin/cli

Conclusion

Shlink URL Shortenet is an open-source project based on PHP that allows you to shorten Long URLs to short URLs. It provides both a web and a CLI interface to work with. Other than shortening links, you can track the visit statistics as well as emails which help in measuring the performance of your links. For a Digital marketer, Shlink provides a unique way of branding your links using custom slugs that you can use on multiple instances which in turn can be shared on social media networks improving the digital campaigns.

More articles:

Your IT Journey Starts Here!

Ready to level up your IT skills? Our new eLearning platform is coming soon to help you master the latest technologies.

Be the first to know when we launch! Join our waitlist now.

Join our Linux and open source community. Subscribe to our newsletter for tips, tricks, and collaboration opportunities!

Recent Post

Leave a Comment

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

Related Post

Greetings and salutations. Our guide today seeks to give a brief of top CLI and GUI tools that developers can […]

Greetings and salutations. In our guide today we will learn how to install KDE Desktop Environment on Oracle Linux 9. […]

greetings and salutations. In this guide, we will learn how to implement Solidity an object-oriented, high-level language for writing smart […]

Let's Connect

Unleash the full potential of your business with CloudSpinx. Our expert solutions specialists are standing by to answer your questions and tailor a plan that perfectly aligns with your unique needs.
You will get a response from our solutions specialist within 12 hours
We understand emergencies can be stressful. For immediate assistance, chat with us now

Contact CloudSpinx today!

Download CloudSpinx Profile

Discover the full spectrum of our expertise and services by downloading our detailed Company Profile. Simply enter your first name, last name, and email address.