bolt Valebyte VPS from $4/mo — NVMe, 60s deploy.

Get a VPS arrow_forward
eco Beginner Tutorial/How-to

Setting Up Your Own Shlink URL Short

calendar_month Jun 22, 2026 schedule 22 min read visibility 30 views
Настройка собственного URL-сокращателя Shlink на VPS с Docker
info

Need a server for this guide? We offer dedicated servers and VPS in 50+ countries with instant setup.

Need a server for this guide?

Deploy a VPS or dedicated server in minutes.

Setting up your own Shlink URL Shortener on a VPS with Docker

TL;DR

In this detailed guide, we will step-by-step set up your own Shlink URL shortening service on a Virtual Private Server (VPS) using Docker and Docker Compose. You will learn how to prepare the server, deploy Shlink along with a database and the Caddy web server for automatic TLS certificate acquisition, and how to ensure backup and maintenance of your installation.

  • You will deploy Shlink – a powerful and flexible open-source URL shortener.
  • You will use Docker and Docker Compose for easy service isolation and management.
  • You will configure automatic HTTPS certificate acquisition with Caddy.
  • You will gain full control over your shortened links and their analytics.
  • You will master the basic principles of Docker application maintenance and backup.
  • You will create a fault-tolerant and secure environment for your service.

What we are setting up and why

Diagram: What we are setting up and why
Diagram: What we are setting up and why

In an era where every character matters, and long, clumsy URLs can deter users and spoil the appearance of a message, link shortening services have become an indispensable tool. Shlink is a self-hosted, open-source solution that allows you to create your own, fully controlled URL shortening service. Unlike popular commercial counterparts such as Bitly or Rebrandly, Shlink provides you with complete freedom and data ownership, as well as the ability to use your own domains for link branding.

By the end of this guide, you will have a fully functional Shlink URL shortener deployed on your own VPS. This means you will be able to create short, memorable links that point to your domain (e.g., my.link/abc instead of long-and-ugly-url.com/some/path/to/resource). In addition to basic shortening, Shlink offers a rich set of features: detailed click analytics (number of clicks, geography, browsers, OS), QR code generation for each link, the ability to set passwords on links, click limits, and expiration dates, as well as a powerful API for integration with your other services. You can manage all these features through a convenient web interface or the command line.

Why self-hosted on a VPS, and not cloud-managed services? The choice in favor of your own VPS is due to several key advantages. Firstly, it's complete control over data. All information about your links and their analytics is stored on your server, not on third-party provider servers. This is critical for projects requiring increased confidentiality or compliance with strict regulatory requirements. Secondly, it's customization. You can configure Shlink to your needs, integrate it with other tools, use your own branded domain without the limitations often imposed by free tiers of cloud services. Thirdly, it's long-term savings. Although renting a VPS requires some initial investment and setup time, for high-load or long-term projects, it often turns out to be significantly cheaper than monthly subscriptions to commercial services, especially with a large volume of links or analytics. Finally, it's an excellent way to deepen your knowledge in server administration and working with Docker, which is a valuable skill for any developer or technical specialist.

Alternatives to Shlink include many commercial solutions such as Bitly, Rebrandly, Short.io, TinyURL, as well as other self-hosted options like Kutt or Polr. However, Shlink stands out for its maturity, active development, rich functionality, and Docker support, making it an ideal choice for deployment on a VPS.

What VPS configuration is needed for this task

Diagram: What VPS configuration is needed for this task
Diagram: What VPS configuration is needed for this task

For deploying Shlink with Docker and a database on a VPS, it's important to choose appropriate hardware resources. Shlink itself is quite lightweight, but Docker, the database (e.g., MariaDB or PostgreSQL), and the web server (Caddy) require a certain minimum.

Minimum requirements:

  • CPU: 1 vCPU (virtual core). This will be sufficient for small projects and testing.
  • RAM: 1 GB. This is an absolute minimum. Docker Engine, OS, and the database consume a significant portion of this memory, leaving little for Shlink itself. Problems may arise during peak loads or updates.
  • Disk: 20 GB NVMe/SSD. NVMe or SSD are highly desirable for database performance. 20 GB will be enough for the OS, Docker images, Shlink data, and logs.
  • Network: 100 Mbps with unlimited traffic or a large allowance (from 1 TB per month). Shlink is not a highly network-dependent service unless you expect millions of clicks per day.

Recommended VPS plan for most cases (relevant for 2026):

For comfortable operation and future scalability, as well as to ensure stability, the following configuration is recommended:

  • CPU: 2 vCPU. Will ensure smooth operation of all components and allow processing more requests simultaneously.
  • RAM: 2-4 GB. 2 GB will be sufficient for most medium projects, 4 GB will provide a large safety margin and allow running additional services if needed.
  • Disk: 40-80 GB NVMe/SSD. A larger disk volume will provide space for database expansion, logs, and potential backups. NVMe will significantly speed up disk operations.
  • Network: 1 Gbps with traffic from 5 TB per month. A gigabit port and sufficient traffic volume will ensure high download speed and request processing, even if your URL shortener becomes very popular.

Finding a VPS with the specified characteristics will not be difficult with most providers. For example, you can choose a VPS with 2 vCPU, 4 GB RAM, and an 80 GB NVMe disk, which will provide excellent performance and future headroom.

When a dedicated server is needed, not a VPS:

A dedicated server becomes necessary when your URL shortener starts handling very large volumes of traffic (tens or hundreds of millions of clicks per month), when maximum database performance is required, or if you plan to host many other resource-intensive applications on the same server. Dedicated servers also offer a higher level of isolation and often broader hardware customization options. For Shlink, this is usually not required unless you are building a global service with billions of redirects.

Location: what it affects

The choice of VPS location matters for several reasons:

  • Latency: The closer the server is to your primary audience or to the servers that the shortened links will point to, the lower the latency during redirection. For a URL shortener, this is not critical, as the redirect itself happens very quickly, but for the overall responsiveness of the admin panel and API, it can be noticeable.
  • Legislation: Depending on where the server is physically located, the laws of that country regarding data storage, privacy, etc., apply to it. Ensure that the chosen jurisdiction meets your requirements.
  • Availability: Some regions may offer more stable and faster network connectivity, as well as better connectivity to different parts of the world.

It is usually recommended to choose a location that is geographically close to your target audience or to yourself to simplify management.

Server Preparation

Diagram: Server Preparation
Diagram: Server Preparation

After gaining access to your new VPS, the first step is to perform minimal setup to ensure security and ease of use. We will use the Ubuntu Server 24.04 LTS distribution (current version for 2026) as the most common and well-documented choice.

1. Connecting via SSH

Connect to the server as the root user (or the user provided by your hosting provider) via SSH. Replace with your server's IP address.


ssh root@

If you are using a password, the system will prompt for it. If you are using SSH keys, ensure your public key is added to the server.

2. System Update

Always start by updating the package list and installing them to the latest version. This will ensure you have all the latest security and stability fixes.


sudo apt update          # Update the list of available packages
sudo apt upgrade -y      # Install all available updates without confirmation
sudo apt autoremove -y   # Remove unnecessary packages

3. Creating a New User with Sudo Privileges

Working as the root user is unsafe. Create a new user and grant them sudo privileges.


sudo adduser shlinkuser                  # Create a new user named shlinkuser
sudo usermod -aG sudo shlinkuser         # Add user shlinkuser to the sudo group

After creating the user, set a strong password for them. Now you can log out of root and log in as the new user:


exit                                     # Log out of root
ssh shlinkuser@             # Log in as the new user

4. Setting up SSH Keys for the New User (Recommended)

To enhance security and convenience, set up SSH key-based login for shlinkuser. If you don't already have an SSH key on your local machine, create one:


ssh-keygen -t rsa -b 4096 -C "[email protected]" # Create a new SSH key (on your local machine)

Then, copy the public key to the server:


ssh-copy-id shlinkuser@     # Copy the public key to the server

Or manually add the content of the file ~/.ssh/id_rsa.pub (on your local machine) to the file ~/.ssh/authorized_keys on the server (for the shlinkuser user).

After this, you can disable password-based SSH login by editing the file /etc/ssh/sshd_config. Find the lines:


PasswordAuthentication yes

And change it to:


PasswordAuthentication no

It is also recommended to disable SSH login for the root user by changing:


PermitRootLogin yes

to:


PermitRootLogin no

After the changes, restart the SSH service:


sudo systemctl restart sshd

Important: Make sure you can log in with your SSH key before disabling password login! Otherwise, you might lose access to the server.

5. Installing and Configuring the UFW Firewall

The Uncomplicated Firewall (UFW) is easy to configure and significantly enhances server security by blocking unwanted incoming connections.


sudo apt install ufw -y                 # Install UFW
sudo ufw allow OpenSSH                  # Allow SSH connections (port 22)
sudo ufw allow http                     # Allow HTTP connections (port 80)
sudo ufw allow https                    # Allow HTTPS connections (port 443)
sudo ufw enable                         # Enable UFW
sudo ufw status                         # Check UFW status

The output of sudo ufw status should show that SSH, HTTP, and HTTPS are allowed.

6. Installing Fail2Ban

Fail2Ban scans server logs (e.g., SSH, web server) for suspicious activity (numerous failed login attempts) and temporarily or permanently blocks the IP addresses of attackers.


sudo apt install fail2ban -y            # Install Fail2Ban
sudo systemctl enable fail2ban          # Enable Fail2Ban autostart on system boot
sudo systemctl start fail2ban           # Start the Fail2Ban service
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # Copy config for local changes

You can edit /etc/fail2ban/jail.local for fine-tuning, for example, to change bantime (block time) or findtime (period over which failed attempts are counted). By default, SSH is already protected.

Software Installation — Step-by-Step

Diagram: Software Installation — Step-by-Step
Diagram: Software Installation — Step-by-Step

Now that the server is prepared, let's proceed with installing the necessary software: Docker Engine and Docker Compose. We will use the current versions available in 2026.

1. Installing Docker Engine

Docker Engine is the core platform for running containers. We will install it from the official Docker repository to always have the latest versions.


# Update the package list and install apt dependencies
sudo apt update
sudo apt install ca-certificates curl gnupg lsb-release -y

# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Add the Docker repository to sources.list
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Update the apt package list after adding the Docker repository
sudo apt update

# Install Docker Engine, Docker CLI, and containerd.io
# Current versions for 2026: Docker Engine 26.x, Docker CLI 26.x
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

# Verify that Docker is installed and running
sudo systemctl status docker

The output of sudo systemctl status docker should show that Docker is active (active (running)).

2. Adding User to Docker Group

To avoid constantly using sudo when working with Docker, add your user to the docker group.


sudo usermod -aG docker shlinkuser       # Add shlinkuser to the docker group
newgrp docker                            # Activate changes for the current session

Now you can test your Docker installation without sudo:


docker run hello-world                   # Run the 'hello-world' test container

If you see a welcome message from Docker, then everything is configured correctly.

3. Creating a Directory for the Shlink Project

Let's create a directory where all Shlink project files will be stored.


mkdir ~/shlink-docker                    # Create the shlink-docker directory in the user's home directory
cd ~/shlink-docker                       # Navigate to the created directory

4. Downloading the Docker Compose File for Shlink

Shlink provides an official docker-compose.yml file that includes all necessary services: Shlink itself, a database (MariaDB by default), and the Caddy web server for reverse proxy and HTTPS. We will use it as a base.

As of 2026, official Shlink and Caddy images are actively maintained. Shlink version 4.x and Caddy version 2.x.


# Download the official docker-compose.yml file
curl -sL https://raw.githubusercontent.com/shlinkio/shlink-web-client/master/docker/docker-compose.yml -o docker-compose.yml

# Download the example .env file
curl -sL https://raw.githubusercontent.com/shlinkio/shlink-web-client/master/docker/.env.example -o .env

# Download the example Caddyfile
curl -sL https://raw.githubusercontent.com/shlinkio/shlink-web-client/master/docker/Caddyfile -o Caddyfile

Now you have three key files in the ~/shlink-docker directory: docker-compose.yml, .env, and Caddyfile. We will edit them in the next step.

Configuration

Diagram: Configuration
Diagram: Configuration

After downloading the base files, you need to configure them for your environment. This includes environment variables, database settings, Caddy web server configuration, and DNS records.

1. Configuring the .env file

The .env file contains environment variables used by Docker Compose to configure services. Edit it by replacing the placeholders with your values. Open the file for editing:


nano .env

Example .env content (be sure to change DB_ROOT_PASSWORD, DB_PASSWORD, DEFAULT_DOMAIN, and API_KEY):


# Database configuration
DB_DRIVER=mariadb
DB_HOST=db
DB_PORT=3306
DB_NAME=shlink
DB_USER=shlink
DB_PASSWORD=your_strong_db_password_here # Be sure to change!
DB_ROOT_PASSWORD=your_strong_db_root_password_here # Be sure to change!

# Shlink configuration
DEFAULT_DOMAIN=your-shlink-domain.com # Replace with your domain (e.g., s.example.com)
DEFAULT_BASE_URL=https://your-shlink-domain.com # Replace with your domain
DEFAULT_TITLE=My Custom Shlink
GEOLITE_LICENSE_KEY=your_geolite_license_key # Optional, for geo-analytics. Get from MaxMind.
API_KEY=your_shlink_api_key_here # Be sure to generate and change!
INITIAL_API_KEY=your_initial_api_key_for_first_login # Optional, for first admin login
SHLINK_WEB_CLIENT_URL=https://shlink-web.your-shlink-domain.com # URL for the web client, if deploying it separately

Explanations:

  • DB_PASSWORD and DB_ROOT_PASSWORD: It is very important to use unique and strong passwords.
  • DEFAULT_DOMAIN: This is the domain that will be used for shortened links. For example, if you want links to look like s.example.com/xyz, then this should be s.example.com. Make sure this domain (or subdomain) points to your VPS's IP address.
  • API_KEY: Generate a long, random key. It will be used for authentication when accessing the Shlink API and in the web client.
  • GEOLITE_LICENSE_KEY: If you want to use geo-analytics, you need to obtain a free GeoLite2 license key from MaxMind (registration required). Without it, geo-analytics will not work.

2. Configuring the Caddyfile

Caddy will act as a reverse proxy, forwarding requests to Shlink and automatically managing HTTPS certificates via Let's Encrypt. Open the file for editing:


nano Caddyfile

Replace your-shlink-domain.com with your domain (the same as in .env).


{
  email [email protected] # Replace with your email for Let's Encrypt notifications
}

your-shlink-domain.com { # Replace with your domain (e.g., s.example.com)
  reverse_proxy shlink:8080
}

# If you are deploying the Shlink web client separately, add a section for it:
# shlink-web.your-shlink-domain.com { # Replace with your domain for the web client
#   root * /app/dist
#   file_server
#   try_files {path} {path}/ /index.html
# }

Explanations:

  • email [email protected]: Enter your active email address. Let's Encrypt will use it for certificate notifications.
  • your-shlink-domain.com: This is the domain through which your Shlink will be accessible. Caddy will automatically obtain an HTTPS certificate for it.
  • shlink:8080: This is the internal Shlink service name within Docker Compose and its port.
  • (Optional) If you are deploying the Shlink web client (shlink-web-client) in the same Docker Compose, you may need a separate section for it, as shown in the commented example. In this guide, we focus on Shlink itself, and the web client can be connected later via API.

3. Configuring the docker-compose.yml file (if necessary)

Usually, the downloaded docker-compose.yml is suitable without changes, but it's worth checking. It defines three services:

  • db: Database (MariaDB).
  • shlink: The Shlink service itself.
  • caddy: Web server/reverse proxy.

Ensure that the image versions are up-to-date (e.g., shlinkio/shlink:4.0.0, mariadb:11.3, caddy:2.7-alpine for 2026). If you plan to use PostgreSQL, you will need to change the db image and the corresponding variables in .env.


# Example docker-compose.yml (abbreviated)
version: '3.8'

services:
  db:
    image: mariadb:11.3 # Current MariaDB version for 2026
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - shlink_db_data:/var/lib/mysql
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: ${DB_NAME}
      MARIADB_USER: ${DB_USER}
      MARIADB_PASSWORD: ${DB_PASSWORD}

  shlink:
    image: shlinkio/shlink:4.0.0 # Current Shlink version for 2026
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "8080:8080" # Internal Shlink port
    depends_on:
      - db
    environment:
      DB_DRIVER: ${DB_DRIVER}
      DB_HOST: ${DB_HOST}
      DB_PORT: ${DB_PORT}
      DB_NAME: ${DB_NAME}
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
      DEFAULT_DOMAIN: ${DEFAULT_DOMAIN}
      DEFAULT_BASE_URL: ${DEFAULT_BASE_URL}
      DEFAULT_TITLE: ${DEFAULT_TITLE}
      GEOLITE_LICENSE_KEY: ${GEOLITE_LICENSE_KEY}
      API_KEY: ${API_KEY}
      INITIAL_API_KEY: ${INITIAL_API_KEY}

  caddy:
    image: caddy:2.7-alpine # Current Caddy version for 2026
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - shlink_caddy_data:/data
    depends_on:
      - shlink

volumes:
  shlink_db_data:
  shlink_caddy_data:

4. Configuring DNS records

Before launching Shlink, you need to ensure that your domain (or subdomain, e.g., s.example.com) points to your VPS's IP address. Go to your domain registrar's (or DNS provider's) control panel and create an A-record:

  • Type: A
  • Name/Host: your subdomain (e.g., s if the full domain is s.example.com) or @ if you are using the main domain.
  • Value/IP address: Your VPS's IP address.

DNS record propagation can take from a few minutes to several hours. You can check the status using the dig command on your local machine:


dig +short your-shlink-domain.com

It should return your VPS's IP address.

5. Launching Shlink with Docker Compose

After all configurations, while in the ~/shlink-docker directory, start all services:


docker compose up -d                     # Start containers in detached mode

This will download the necessary Docker images (if not present locally), create containers, configure the network, and start the services. The first launch may take some time.

You can check the status of running containers with the command:


docker compose ps                        # Shows the status of running containers

All containers (db, shlink, caddy) should be in running status.

6. Verifying functionality

After launching, you need to ensure that Shlink is working correctly.

  • Caddy Check: Open your domain (https://your-shlink-domain.com) in your browser. You should see a message from Shlink saying "It works!" or the Shlink web client page if you have deployed it. If you see a certificate error, it's possible that DNS has not yet updated or there is an issue with the Caddyfile.
  • Shlink API Check: Make a request to the Shlink API to ensure it is responding. Replace your-shlink-domain.com with your domain.

curl -I https://your-shlink-domain.com/rest/v4/health

In the response, you should see an HTTP/2 200 status and a server: Caddy header, which indicates successful operation.

If everything is in order, your Shlink is ready to use! You can start shortening links via the API or through the official Shlink web client, by specifying your Shlink instance URL and API key.

Backups and Maintenance

Diagram: Backups and Maintenance
Diagram: Backups and Maintenance

Setting up Shlink is just the first step. Regular backups and proper maintenance are crucial for ensuring the long-term stability and security of your URL shortener.

1. What to Back Up

The following components are critical for Shlink:

  • Database: Contains all shortened links, click statistics, domains, and Shlink settings. This is the most important component.
  • Configuration Files: Primarily .env and Caddyfile. They define how your services operate.
  • Caddy Data: The shlink_caddy_data directory (from docker-compose.yml) contains Let's Encrypt TLS certificates and other Caddy data. It's important to preserve it to avoid issues with re-obtaining certificates after restoration.

2. Simple Auto-Backup Script

We will create a simple script that will dump the database and archive important files. This script can be scheduled to run using cron.

Create the file backup_shlink.sh in the ~/shlink-docker directory:


nano ~/shlink-docker/backup_shlink.sh

Add the following content (replace your_strong_db_root_password_here with your actual password from .env):


#!/bin/bash

# Backup directory
BACKUP_DIR="/var/backups/shlink"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
DB_CONTAINER_NAME="shlink-docker-db-1" # Database container name (check via docker ps)
DB_NAME="shlink"
DB_USER="root"
DB_ROOT_PASSWORD="your_strong_db_root_password_here" # Use DB_ROOT_PASSWORD from .env

# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"

echo "Starting Shlink backup at $DATE..."

# 1. Database Backup
echo "Dumping database..."
docker exec "$DB_CONTAINER_NAME" mariadb-dump -u "$DB_USER" -p"$DB_ROOT_PASSWORD" "$DB_NAME" > "$BACKUP_DIR/$DB_NAME-$DATE.sql"
if [ $? -eq 0 ]; then
    echo "Database dump successfully created: $BACKUP_DIR/$DB_NAME-$DATE.sql"
else
    echo "Error creating database dump!"
fi

# 2. Archiving configuration files and Caddy data
echo "Archiving configuration files and Caddy data..."
tar -czvf "$BACKUP_DIR/shlink-config-caddy-data-$DATE.tar.gz" -C ~/shlink-docker .env Caddyfile shlink_caddy_data
if [ $? -eq 0 ]; then
    echo "Configuration and Caddy data archive successfully created: $BACKUP_DIR/shlink-config-caddy-data-$DATE.tar.gz"
else
    echo "Error creating configuration and Caddy data archive!"
fi

# 3. Deleting old backups (older than 7 days)
echo "Deleting old backups..."
find "$BACKUP_DIR" -type f -name "*.sql" -mtime +7 -delete
find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +7 -delete
echo "Old backups deleted."

echo "Shlink backup completed."

Make the script executable:


chmod +x ~/shlink-docker/backup_shlink.sh

3. Setting up Cron for Automatic Backups

Add the script to cron for daily execution. For example, at 03:00 AM.


crontab -e

Add the following line to the end of the file:


0 3 * * * /home/shlinkuser/shlink-docker/backup_shlink.sh >> /var/log/shlink_backup.log 2>&1

This line runs the script every day at 3 AM and redirects its output to the log file /var/log/shlink_backup.log.

4. Where to Store Backups (External Storage)

Storing backups on the same server as the main service is risky. In case of a VPS disk failure, you will lose both the service and the backups. It is recommended to use external storage:

  • Object Cloud Storage (S3-compatible): AWS S3, Backblaze B2, DigitalOcean Spaces, MinIO. These are reliable and scalable solutions. You can use utilities like s3cmd or rclone to automatically synchronize local backups with the cloud.
  • Separate VPS: If you have a second VPS, you can set up rsync or scp to copy backups to it.
  • Local NAS/Server: For personal use, you can copy backups to a home network-attached storage.

Example of adding rclone to send backups to S3-compatible storage:


# Install rclone
sudo apt install rclone -y

# Configure rclone (follow on-screen instructions, choose an S3-compatible provider)
rclone config

# Add a line to backup_shlink.sh script for synchronization after creating the archive:
# rclone sync "$BACKUP_DIR" "your_rclone_remote_name:your_bucket_name/shlink_backups"

5. Updates: rolling vs maintenance window

Regular updates are important for security and new features.

  • OS Update: Run sudo apt update && sudo apt upgrade -y every few weeks or months. This may require a server reboot.
  • Docker Container Updates:
    • Shlink: To update Shlink to a new version, you need to change the image tag in docker-compose.yml (e.g., from shlinkio/shlink:4.0.0 to shlinkio/shlink:4.1.0).
    • Database and Caddy: Similarly, update the mariadb and caddy image tags.

    After changing docker-compose.yml, execute:

    
    cd ~/shlink-docker
    docker compose pull                         # Downloads new image versions
    docker compose up -d                        # Recreates containers with new images
    docker compose prune                        # Removes old, unused images
                

    This will result in a brief service downtime (a few seconds or minutes), so it's best to perform it during a "maintenance window" when traffic is minimal. Always make a backup before updating!

Troubleshooting + FAQ

Various issues may arise during the installation and operation of Shlink. Here, we will cover the most common ones and provide recommendations for their resolution.

Why isn't my domain working or Caddy not getting a certificate?

This is one of the most common issues.

What to check:

  • DNS Records: Ensure that the A-record for your domain (e.g., s.example.com) is correctly configured and points to your VPS's IP address. Use dig your-shlink-domain.com on your local machine to verify this. DNS propagation can take up to 24 hours, but usually happens faster.
  • Ports 80 and 443: Check that ports 80 (HTTP) and 443 (HTTPS) are open on your VPS. Caddy must be able to listen on these ports to obtain Let's Encrypt certificates. Use sudo ufw status or sudo ss -tulpn | grep -E ':(80|443)'. Ensure no other service is occupying these ports.
  • Caddyfile: Check the Caddyfile syntax for typos. Ensure that the domain in the Caddyfile exactly matches the one you are using.
  • Caddy Logs: Check the Caddy container logs for errors: docker compose logs caddy. Look for error messages related to certificate acquisition.

How to fix: Correct the DNS record, open ports in the firewall, fix the Caddyfile, and restart Caddy: docker compose restart caddy.

Shlink is not shortening links or is throwing API errors.

If the Shlink web interface or API is not working correctly, the problem might be with Shlink's configuration or the database connection.

What to check:

  • Shlink Logs: Check the Shlink container logs: docker compose logs shlink. Look for errors related to database connection or application errors.
  • .env file: Ensure that all environment variables, especially those related to the database (DB_HOST, DB_USER, DB_PASSWORD, DB_NAME) and domain (DEFAULT_DOMAIN, API_KEY), are correctly configured and accurate.
  • Database Availability: Ensure that the database container (db) is running and operational: docker compose ps. Try connecting to the database from within the Shlink container: docker exec -it shlink-docker-shlink-1 mariadb -u shlink -p (enter password).

How to fix: Correct errors in .env or docker-compose.yml, then restart the services: docker compose up -d --force-recreate shlink.

What is the minimum suitable VPS configuration?

For a minimal Shlink installation with Docker, a database, and Caddy, you will need a VPS with 1 vCPU, 1 GB RAM, and 20 GB NVMe/SSD disk. This will be sufficient for personal use or a small project with a moderate number of links and traffic. However, for more stable operation and growth potential, as well as comfortable operation of the OS and Docker services, it is recommended to have at least 2 vCPU and 2-4 GB RAM. An NVMe/SSD disk will significantly improve database performance. You might consider VPS options with 2 vCPU and 4 GB RAM, which will provide a good balance between cost and performance.

What to choose — VPS or dedicated for this task?

For most Shlink use cases, a VPS (Virtual Private Server) is the optimal choice. Shlink itself is not an extremely resource-intensive application, and modern VPS offerings provide sufficient performance, flexibility, and scalability at an affordable price. A VPS is ideal for individual developers, small teams, early-stage SaaS startups, and personal projects. A Dedicated server is only necessary in very specific cases: if you anticipate extremely high loads (tens of millions of clicks per day), if you require maximum I/O performance for the database, or if you plan to host many other resource-intensive applications on the same server that demand full isolation of hardware resources. In most cases, overpaying for a dedicated server for Shlink makes no sense.

How to update Shlink or other components?

Updating Docker Compose components involves changing the image tag in the docker-compose.yml file to a new version (e.g., from shlinkio/shlink:4.0.0 to shlinkio/shlink:4.1.0) and then executing the following commands:


docker compose pull                 # Downloads new image versions
docker compose up -d                # Recreates containers with new images
docker image prune                  # Removes old, unused images
    

Always create a full backup of the database and configuration files before updating. OS updates are performed via sudo apt update && sudo apt upgrade -y.

Can I use another web server instead of Caddy (e.g., Nginx)?

Yes, you can use Nginx or any other reverse proxy. To do this, you will need to manually configure Nginx to proxy requests to the Shlink container (usually on internal port 8080) and separately configure the acquisition and renewal of TLS certificates (e.g., using Certbot). Caddy was chosen in this guide due to its ease of configuration and automatic HTTPS management, which significantly simplifies the process for most users.

How to get a GeoLite2 License Key?

To obtain a GeoLite2 License Key, which Shlink uses for geo-analytics, you need to register on the MaxMind website. After registration, you can generate your key in the "My Account" -> "Manage License Keys" section. Insert this key into the GEOLITE_LICENSE_KEY variable in the .env file.

Conclusions and Next Steps

Diagram: Conclusions and Next Steps
Diagram: Conclusions and Next Steps

Congratulations! You have successfully set up and deployed your own Shlink URL shortener on your VPS using Docker. You now have a powerful link management tool that provides full control over your data, branding through your own domain, and detailed click analytics. This experience has not only provided you with a functional service but also strengthened your skills in working with Docker, Linux, and server administration.

Where to go next?

  • Connecting the Shlink web client: Deploy the official Shlink web client (Shlink Web Client) in a separate Docker container or on another subdomain. This will provide you with a convenient graphical interface for managing links without needing to use the API or command line.
  • Integration with other services: Use Shlink's powerful API to integrate link shortening into your own applications, CRM systems, or automation scripts.
  • Monitoring and Logging: Set up more advanced monitoring for your VPS and Docker containers using tools like Prometheus/Grafana or ELK Stack to track performance and identify potential issues.
  • Scaling: If your Shlink becomes very popular, consider scaling the database to a separate server or using a Docker Swarm/Kubernetes cluster to ensure high availability and handle large loads.

Was this guide helpful?

Setup custom Shlink URL shortener on VPS with Docker
support_agent
Valebyte Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.