What You'll Build
- Shlink API Server — The core URL shortening engine, handling redirects, analytics, and short URL management via REST API and CLI
- MariaDB Database — Persistent storage for your short URLs, visit statistics, API keys, and configuration
- Shlink Web Client — A browser-based dashboard for creating and managing short URLs without touching the command line
- Nginx Reverse Proxy — Handles HTTPS termination with Let's Encrypt certificates and routes traffic to Shlink
- GeoLite2 Integration — Visitor geolocation tracking powered by MaxMind's free GeoLite2 database
Prerequisites
RamNode VPS Requirements
| Resource | Recommendation |
|---|---|
| Plan | KVM VPS — 2 GB RAM / 1 vCPU (starting at $4/month) |
| OS | Ubuntu 22.04 LTS or 24.04 LTS |
| Storage | 15 GB SSD minimum (included in base plan) |
| Domain | A registered domain with DNS pointed to your VPS IP |
| DNS Records | A record for your short domain (e.g., s.example.com) |
Software Requirements
- Docker Engine 20.10+
- Docker Compose v2+
- A non-root user with sudo privileges
- A GeoLite2 license key from MaxMind (free signup at maxmind.com)
GeoLite2 License Key: Shlink uses MaxMind's GeoLite2 database to geolocate visitors. Create an account at maxmind.com, navigate to Account → Manage License Keys, and generate a new key.
Provision Your RamNode VPS
ssh root@YOUR_VPS_IPadduser deploy
usermod -aG sudo deploy
su - deploysudo apt update && sudo apt upgrade -yInstall Docker and Docker Compose
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp dockerdocker --version
docker compose versionConfigure DNS
Point your short domain to your RamNode VPS. Create the following DNS records:
| Type | Name | Value | TTL |
|---|---|---|---|
| A | s.example.com | YOUR_VPS_IP | 300 |
| A | admin.example.com | YOUR_VPS_IP | 300 |
The first record is for your short URLs (public-facing). The second is optional, for self-hosting the Shlink web client on a separate subdomain.
Create the Environment File
mkdir -p ~/shlink && cd ~/shlink
nano ~/shlink/.env# Shlink Configuration
DEFAULT_DOMAIN=s.example.com
IS_HTTPS_ENABLED=true
GEOLITE_LICENSE_KEY=your_maxmind_license_key
# Database Configuration
DB_DRIVER=maria
DB_NAME=shlink
DB_USER=shlink
DB_PASSWORD=generate_a_strong_password_here
DB_HOST=shlink_db
# MariaDB Root Password
MARIADB_ROOT_PASSWORD=generate_another_strong_password
# Shlink API Key (generate a random string)
INITIAL_API_KEY=generate_a_random_api_key_here
# Timezone
TZ=America/New_Yorkopenssl rand -base64 32Security Note: Never commit your .env file to version control. Add it to .gitignore if you track your configuration in Git.
Create the Docker Compose File
services:
shlink:
image: shlinkio/shlink:stable
container_name: shlink_app
restart: unless-stopped
depends_on:
- shlink_db
environment:
- DEFAULT_DOMAIN=${DEFAULT_DOMAIN}
- IS_HTTPS_ENABLED=${IS_HTTPS_ENABLED}
- GEOLITE_LICENSE_KEY=${GEOLITE_LICENSE_KEY}
- DB_DRIVER=${DB_DRIVER}
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- DB_HOST=${DB_HOST}
- INITIAL_API_KEY=${INITIAL_API_KEY}
- TZ=${TZ}
ports:
- "127.0.0.1:8080:8080"
networks:
- shlink_net
shlink_db:
image: mariadb:11
container_name: shlink_db
restart: unless-stopped
environment:
- MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD}
- MARIADB_DATABASE=${DB_NAME}
- MARIADB_USER=${DB_USER}
- MARIADB_PASSWORD=${DB_PASSWORD}
volumes:
- shlink_db_data:/var/lib/mysql
networks:
- shlink_net
shlink_web:
image: shlinkio/shlink-web-client:stable
container_name: shlink_web
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
networks:
- shlink_net
volumes:
shlink_db_data:
networks:
shlink_net:
driver: bridgeArchitecture Note: The Shlink API binds to 127.0.0.1:8080 and the web client to 127.0.0.1:8081. This ensures they are only accessible through the Nginx reverse proxy, never directly from the internet.
Deploy the Stack
cd ~/shlink
docker compose up -ddocker compose psYou should see shlink_app, shlink_db, and shlink_web all showing as "Up". Wait 15–30 seconds for the database to initialize on first startup.
curl -I http://127.0.0.1:8080A 404 response is expected when accessing the root URL — this confirms Shlink is running. Short URL redirects and the API are what respond to real requests.
Configure Nginx Reverse Proxy with SSL
sudo apt install nginx certbot python3-certbot-nginx -yserver {
listen 80;
server_name s.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Web Client Configuration (Optional)
server {
listen 80;
server_name admin.example.com;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}sudo ln -s /etc/nginx/sites-available/shlink /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/shlink-admin /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Obtain SSL certificates
sudo certbot --nginx -d s.example.com -d admin.example.comWeb Client Security: The Shlink web client has no built-in authentication. Do NOT expose it directly to the public internet without additional protection such as HTTP basic auth, VPN, or IP whitelisting. Alternatively, use the hosted client at app.shlink.io which authenticates via your API key.
Create Your First Short URL
Using the CLI
# Create a short URL
docker exec shlink_app shlink short-url:create \
--longUrl https://www.example.com/very/long/article-url
# Create with custom slug
docker exec shlink_app shlink short-url:create \
--longUrl https://www.example.com/promo \
--customSlug promo2025
# List all short URLs
docker exec shlink_app shlink short-url:list
# View visit stats for a short URL
docker exec shlink_app shlink short-url:visits promo2025Using the REST API
curl -X POST https://s.example.com/rest/v3/short-urls \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"longUrl": "https://example.com/my-page"}'Using the Web Client
Navigate to your admin subdomain (e.g., https://admin.example.com) or visit app.shlink.io. Enter your Shlink server URL and API key to connect. From the dashboard you can create short URLs, view analytics, manage tags, and configure default redirects.
Ongoing Maintenance
Troubleshooting
| Issue | Solution |
|---|---|
| Container won't start | Check logs with docker logs shlink_app. Verify .env values are correct and the database container is running. |
| 502 Bad Gateway | Shlink container may still be starting. Wait 30 seconds and retry. Verify with docker compose ps. |
| SSL certificate error | Ensure DNS A records are propagated (check with dig s.example.com). Re-run certbot if needed. |
| GeoLite2 download fails | Verify your GEOLITE_LICENSE_KEY is correct. Keys can be regenerated from your MaxMind dashboard. |
| Database connection refused | Confirm DB_HOST matches the database service name (shlink_db). Check that the DB container is healthy. |
| Short URLs return 404 | Verify DEFAULT_DOMAIN matches your actual domain. Check that IS_HTTPS_ENABLED matches your actual protocol. |
Shlink Deployed Successfully!
Your self-hosted URL shortener is now running. Create branded short links, track click analytics with geolocation, and manage everything through the REST API, CLI, or web dashboard.
