Shlink: Modern URL Shortener
Feature-rich URL shortener with analytics. API-first design with QR codes.
Introduction
In today’s digital landscape, URL shorteners have become essential tools for sharing links efficiently, tracking clicks, and managing marketing campaigns. While services like Bitly and TinyURL dominate the market, they come with limitations: paywalls for advanced features, data privacy concerns, and dependency on third-party services. Enter Shlink—a powerful, modern, open-source URL shortener that you can self-host in your homelab.
Shlink stands out in the self-hosted ecosystem with its polished REST API, real-time analytics, QR code generation, custom domain support, and comprehensive tracking capabilities. Whether you’re managing marketing campaigns, sharing links on social media, or simply want to create memorable short URLs for your services, Shlink provides enterprise-grade features without the enterprise price tag.
What you’ll learn:
- Deploy Shlink with Docker Compose in under 15 minutes
- Configure custom domains for branded short links
- Use the REST API to automate URL creation
- Set up the web interface for visual management
- Track visits with detailed analytics and geolocation
- Integrate QR codes for mobile-friendly sharing
- Configure browser extensions for one-click shortening
Prerequisites:
- Docker and Docker Compose installed
- Basic understanding of Docker containers
- A domain name (optional, but recommended for custom short domains)
- 512MB+ RAM available
- Basic familiarity with REST APIs
Why Choose Shlink?
The URL Shortening Landscape
Traditional URL shorteners present several challenges:
- Privacy concerns: Your links and analytics data stored on third-party servers
- Feature limitations: Advanced analytics, custom domains, and API access locked behind paywalls
- Reliability: Services can shut down, taking your links with them
- Branding: Generic short domains don’t reinforce your brand
Shlink solves these issues elegantly:
- Complete privacy: All data stays on your infrastructure
- Free features: API, analytics, custom domains, QR codes—all included
- Self-reliance: You control the service and data retention
- Custom branding: Use your own short domain like go.yourdomain.com
Key Features at a Glance
| Feature | Details |
|---|---|
| REST API | Complete API for automation and integrations |
| Custom Domains | Support unlimited custom short domains |
| QR Codes | Automatic QR code generation for every short URL |
| Analytics | Visit tracking with geolocation, referrers, devices, browsers |
| Tags & Organization | Tag URLs for easy categorization and filtering |
| Real-time Stats | WebSocket-powered real-time visit notifications |
| Import/Export | Import from Bitly, export data anytime |
| Multi-user | API key management for multiple users/applications |
| Database Support | MySQL, MariaDB, PostgreSQL, SQL Server, or SQLite |
Understanding URL Shortening Architecture
How Shlink Works
When someone accesses a short URL, here’s what happens:
- Request arrives: User visits
https://go.yourdomain.com/abc123 - Lookup: Shlink queries database for short code
abc123 - Tracking: Records visit metadata (IP, user agent, referrer, timestamp)
- Redirect: HTTP 302/301 redirect to original long URL
- Analytics: Visit data processed for real-time analytics
Tracking Capabilities
Shlink captures comprehensive visit data:
- Geographic data: Country, city, region via IP geolocation
- Device info: Operating system, browser, device type
- Referrer tracking: Where visitors came from
- Click timing: Date, time, timezone
- Bot detection: Distinguishes humans from automated traffic
Installation: Docker Compose Method
Step 1: Create Project Directory
mkdir -p ~/shlink
cd ~/shlink Step 2: Create docker-compose.yml
This configuration deploys Shlink with PostgreSQL database:
version: '3.8'
services:
shlink:
image: shlinkio/shlink:stable
container_name: shlink
restart: always
ports:
- "8080:8080"
environment:
DEFAULT_DOMAIN: go.yourdomain.com
IS_HTTPS_ENABLED: "true"
GEOLITE_LICENSE_KEY: YOUR_LICENSE_KEY_HERE
DB_DRIVER: postgres
DB_NAME: shlink
DB_USER: shlink
DB_PASSWORD: secure_password_here
DB_HOST: database
DB_PORT: 5432
# Timezone configuration
TIMEZONE: America/New_York
# Short code generation settings
DEFAULT_SHORT_CODES_LENGTH: 5
# Redirect behavior
REDIRECT_STATUS_CODE: 302
# Tracking options
ANONYMIZE_REMOTE_ADDR: "false"
depends_on:
- database
networks:
- shlink-net
database:
image: postgres:16-alpine
container_name: shlink-db
restart: always
environment:
POSTGRES_DB: shlink
POSTGRES_USER: shlink
POSTGRES_PASSWORD: secure_password_here
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- shlink-net
# Optional: Web client for visual management
shlink-web-client:
image: shlinkio/shlink-web-client:stable
container_name: shlink-web
restart: always
ports:
- "8081:80"
networks:
- shlink-net
networks:
shlink-net:
driver: bridge Configuration notes:
DEFAULT_DOMAIN: Your short URL domain (without https://)GEOLITE_LICENSE_KEY: Free key from MaxMind for geolocation (see below)DEFAULT_SHORT_CODES_LENGTH: Length of generated short codes (5 = abc12)REDIRECT_STATUS_CODE: 302 (temporary) or 301 (permanent) redirectsANONYMIZE_REMOTE_ADDR: Set to “true” for GDPR compliance (anonymizes last IP octet)
Step 3: Get GeoLite2 License Key (Optional but Recommended)
For geolocation tracking:
- Visit: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
- Sign up for free account
- Generate license key
- Replace
YOUR_LICENSE_KEY_HEREin docker-compose.yml
Without GeoLite key, geolocation tracking will be disabled, but all other features work normally.
Step 4: Secure Your Configuration
# Update database password
sed -i 's/secure_password_here/YOUR_STRONG_PASSWORD/g' docker-compose.yml
# Set permissions
chmod 600 docker-compose.yml Step 5: Start Services
docker compose up -d Step 6: Verify Installation
# Check containers are running
docker ps | grep shlink
# View logs
docker logs shlink
docker logs shlink-db
# Test API endpoint
curl http://localhost:8080/rest/health Expected health check response:
{
"status": "pass",
"version": "3.7.0",
"links": {
"about": "https://shlink.io"
}
} Initial Configuration
Generate API Key
API keys are required for all Shlink operations:
# Generate API key with full permissions
docker exec -it shlink shlink api-key:generate
# Expected output:
# Generated API key: a1b2c3d4-e5f6-7890-abcd-ef1234567890 Save this API key securely - you’ll need it for all API calls and web client configuration.
Configure Web Client
- Navigate to:
http://YOUR_SERVER_IP:8081 - Click “Add server”
- Configure connection:
- Server name: My Homelab
- Server URL:
http://YOUR_SERVER_IP:8080 - API key: Paste key from above
- Click “Add server”
- Select server from dropdown
You should now see the Shlink dashboard with analytics overview.
Creating Short URLs
Method 1: Web Interface
The easiest method for manual URL creation:
- Open web client at
http://YOUR_SERVER_IP:8081 - Click “Create short URL”
- Configure URL:
- Long URL:
https://www.example.com/very/long/path/to/page - Custom slug (optional):
my-custom-slugbecomes/my-custom-slug - Tags: marketing, social-media (for organization)
- Max visits: Leave empty for unlimited
- Valid since/until: Set date restrictions if needed
- Domain: Select from configured domains
- Long URL:
- Click “Create”
Result: Short URL like https://go.yourdomain.com/abc12 created instantly.
Method 2: REST API
For automation and programmatic creation:
# Create basic short URL
curl -X POST http://localhost:8080/rest/v3/short-urls -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{
"longUrl": "https://www.example.com/very/long/path",
"tags": ["api", "automation"]
}' Response:
{
"shortCode": "abc12",
"shortUrl": "http://go.yourdomain.com/abc12",
"longUrl": "https://www.example.com/very/long/path",
"dateCreated": "2025-01-15T10:30:00Z",
"tags": ["api", "automation"],
"meta": {
"validSince": null,
"validUntil": null,
"maxVisits": null
},
"domain": null,
"title": null,
"crawlable": false,
"forwardQuery": true
} Method 3: Custom Short Codes
Create memorable short codes:
curl -X POST http://localhost:8080/rest/v3/short-urls -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{
"longUrl": "https://github.com/shlinkio/shlink",
"customSlug": "gh-shlink",
"tags": ["github", "project"]
}' Result: Creates https://go.yourdomain.com/gh-shlink
Advanced URL Options
Create URL with all advanced features:
curl -X POST http://localhost:8080/rest/v3/short-urls -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{
"longUrl": "https://example.com/limited-time-offer",
"customSlug": "offer-jan",
"tags": ["marketing", "january-2025"],
"maxVisits": 1000,
"validSince": "2025-01-15T00:00:00Z",
"validUntil": "2025-01-31T23:59:59Z",
"findIfExists": true,
"title": "January Special Offer",
"crawlable": false,
"forwardQuery": true
}' Options explained:
maxVisits: URL expires after 1,000 clicksvalidSince/validUntil: URL only works during January 2025findIfExists: Returns existing URL if long URL already shortenedcrawlable: Controls if search engines can index the short URLforwardQuery: Forward query parameters to destination
Custom Domain Setup
Use your own domain for branded short links.
DNS Configuration
Add DNS record for your short domain:
Type: A
Name: go.yourdomain.com
Value: YOUR_SERVER_IP
TTL: 3600 Or for Cloudflare Tunnel users:
Type: CNAME
Name: go.yourdomain.com
Value: YOUR_TUNNEL_ID.cfargotunnel.com
TTL: Auto Reverse Proxy Configuration
Caddy (recommended for simplicity):
# /etc/caddy/Caddyfile
go.yourdomain.com {
reverse_proxy localhost:8080
} sudo systemctl reload caddy Nginx:
# /etc/nginx/sites-available/shlink
server {
server_name go.yourdomain.com;
location / {
proxy_pass http://localhost: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;
}
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/go.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/go.yourdomain.com/privkey.pem;
}
server {
listen 80;
server_name go.yourdomain.com;
return 301 https://$server_name$request_uri;
} sudo ln -s /etc/nginx/sites-available/shlink /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx Test Custom Domain
# Create short URL with custom domain
curl -X POST http://localhost:8080/rest/v3/short-urls -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{
"longUrl": "https://example.com",
"domain": "go.yourdomain.com"
}'
# Test redirect
curl -I https://go.yourdomain.com/abc12 Expected response headers:
HTTP/2 302
location: https://example.com API Usage Examples
List All Short URLs
curl -X GET "http://localhost:8080/rest/v3/short-urls?page=1&itemsPerPage=10" -H "X-Api-Key: YOUR_API_KEY" Get Single Short URL Details
curl -X GET http://localhost:8080/rest/v3/short-urls/abc12 -H "X-Api-Key: YOUR_API_KEY" Edit Existing Short URL
curl -X PATCH http://localhost:8080/rest/v3/short-urls/abc12 -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{
"longUrl": "https://example.com/updated-destination",
"maxVisits": 500
}' Delete Short URL
curl -X DELETE http://localhost:8080/rest/v3/short-urls/abc12 -H "X-Api-Key: YOUR_API_KEY" Get Visit Statistics
# Get visits for specific short URL
curl -X GET http://localhost:8080/rest/v3/short-urls/abc12/visits -H "X-Api-Key: YOUR_API_KEY"
# Get visits with date filtering
curl -X GET "http://localhost:8080/rest/v3/short-urls/abc12/visits?startDate=2025-01-01&endDate=2025-01-31" -H "X-Api-Key: YOUR_API_KEY" Python Automation Script
import requests
from datetime import datetime
class ShlinkAPI:
def __init__(self, base_url, api_key):
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.headers = {
'X-Api-Key': api_key,
'Content-Type': 'application/json'
}
def create_short_url(self, long_url, custom_slug=None, tags=None):
"""Create a short URL"""
endpoint = f"{self.base_url}/rest/v3/short-urls"
payload = {'longUrl': long_url}
if custom_slug:
payload['customSlug'] = custom_slug
if tags:
payload['tags'] = tags
response = requests.post(endpoint, json=payload, headers=self.headers)
return response.json()
def get_visits(self, short_code):
"""Get visit statistics for short URL"""
endpoint = f"{self.base_url}/rest/v3/short-urls/{short_code}/visits"
response = requests.get(endpoint, headers=self.headers)
return response.json()
def list_short_urls(self, page=1, items_per_page=10, tags=None):
"""List short URLs with optional filtering"""
endpoint = f"{self.base_url}/rest/v3/short-urls"
params = {'page': page, 'itemsPerPage': items_per_page}
if tags:
params['tags[]'] = tags
response = requests.get(endpoint, params=params, headers=self.headers)
return response.json()
# Usage example
shlink = ShlinkAPI('http://localhost:8080', 'YOUR_API_KEY')
# Create short URL
result = shlink.create_short_url(
'https://example.com/long-url',
custom_slug='my-link',
tags=['python', 'automation']
)
print(f"Short URL created: {result['shortUrl']}")
# Get statistics
visits = shlink.get_visits('my-link')
print(f"Total visits: {visits['visits']['pagination']['totalItems']}") Analytics and Tracking
Accessing Analytics
Web Interface:
- Open web client
- Click on any short URL
- View detailed analytics:
- Total visits over time (graph)
- Geographic distribution (map)
- Top referrers
- Device breakdown
- Browser statistics
- Operating systems
API:
# Get comprehensive visit data
curl -X GET http://localhost:8080/rest/v3/short-urls/abc12/visits -H "X-Api-Key: YOUR_API_KEY" Understanding Visit Metadata
Each visit records:
{
"referer": "https://twitter.com",
"date": "2025-01-15T14:23:45+00:00",
"userAgent": "Mozilla/5.0...",
"visitLocation": {
"countryCode": "US",
"countryName": "United States",
"regionName": "California",
"cityName": "San Francisco",
"latitude": 37.7749,
"longitude": -122.4194,
"timezone": "America/Los_Angeles"
},
"potentialBot": false
} Export Analytics Data
# Export to CSV for analysis
curl -X GET "http://localhost:8080/rest/v3/short-urls/abc12/visits?format=csv" -H "X-Api-Key: YOUR_API_KEY" -o visits.csv QR Code Generation
Every short URL automatically has a QR code available.
Get QR Code
Via Web Interface:
- Open short URL details
- Click “QR Code” button
- Download or display QR code
Via API:
# Get QR code PNG image
curl -X GET http://localhost:8080/rest/v3/short-urls/abc12/qr-code -H "X-Api-Key: YOUR_API_KEY" -o qrcode.png
# Customize QR code size and format
curl -X GET "http://localhost:8080/rest/v3/short-urls/abc12/qr-code?size=500&format=svg" -H "X-Api-Key: YOUR_API_KEY" -o qrcode.svg Options:
size: Pixel width/height (default: 300)format: png or svg (default: png)margin: Margin around QR code (default: 0)
Embed QR Code in Websites
<!-- Direct image embed -->
<img src="https://go.yourdomain.com/abc12/qr-code?size=300"
alt="QR Code" />
<!-- With custom styling -->
<img src="https://go.yourdomain.com/abc12/qr-code?size=400&format=png"
alt="Scan to visit"
style="border: 2px solid #333; border-radius: 8px; padding: 10px;" /> Browser Extensions
Shlink offers official browser extensions for quick URL shortening.
Install Extension
Chrome/Edge:
- Visit: https://chrome.google.com/webstore
- Search: “Shlink URL Shortener”
- Click “Add to Chrome/Edge”
Firefox:
- Visit: https://addons.mozilla.org
- Search: “Shlink URL Shortener”
- Click “Add to Firefox”
Configure Extension
- Click extension icon in browser toolbar
- Click “Add server”
- Enter settings:
- Server URL:
https://go.yourdomain.com - API key: Your API key
- Server name: My Homelab
- Server URL:
- Click “Save”
Using Extension
Method 1: Right-click context menu
- Right-click any link on webpage
- Select “Shorten with Shlink”
- Short URL copied to clipboard
Method 2: Extension popup
- Navigate to page you want to shorten
- Click extension icon
- Click “Shorten current URL”
- Short URL copied automatically
Method 3: Selection shortening
- Highlight URL text on page
- Right-click selection
- Select “Shorten selected URL”
Advanced Features
Import from Bitly
Migrate existing short URLs from Bitly:
docker exec -it shlink shlink short-url:import bitly YOUR_BITLY_API_KEY Custom 404 Pages
Configure custom page for invalid short codes:
# Update docker-compose.yml environment
NOT_FOUND_REDIRECT_TO: https://yourdomain.com/404 Restart container:
docker compose restart shlink URL Validation
Enable strict URL validation:
# Add to docker-compose.yml environment
VALIDATE_URLS: "true" This checks that destination URLs are reachable before creating short URLs.
Multi-Domain Support
Add additional short domains:
# Create short URL with specific domain
curl -X POST http://localhost:8080/rest/v3/short-urls -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{
"longUrl": "https://example.com",
"domain": "alt.yourdomain.com"
}' Configure DNS and reverse proxy for each domain following the same process as primary domain.
Troubleshooting
Issue 1: Short URLs Return 404
Symptoms: Accessing short URL shows “Not Found” error.
Diagnosis:
# Check database connection
docker logs shlink | grep -i database
# Verify short code exists
docker exec -it shlink shlink short-url:list Solutions:
- Ensure database container is running:
docker ps | grep shlink-db - Check database password matches in both containers
- Verify short code spelling (case-sensitive)
Issue 2: Geolocation Not Working
Symptoms: Visit analytics show “Unknown location”.
Solutions:
- Verify GeoLite license key is valid
- Check GeoIP database downloaded:
docker exec -it shlink ls -la /etc/shlink/data/GeoLite2-City.mmdb - If missing, restart container to trigger download:
docker compose restart shlink
Issue 3: API Returns 401 Unauthorized
Symptoms: API calls fail with authentication error.
Solutions:
- Verify API key is correct
- Check API key hasn’t been revoked:
docker exec -it shlink shlink api-key:list - Generate new API key if needed:
docker exec -it shlink shlink api-key:generate
Issue 4: High Database Size
Symptoms: Database consuming excessive disk space.
Solutions:
# Check visit count
docker exec -it shlink shlink visit:stats
# Delete visits older than 6 months
docker exec -it shlink shlink visit:delete --older-than=6months
# Optimize database
docker exec -it shlink-db psql -U shlink -d shlink -c "VACUUM FULL;" Issue 5: Slow Redirects
Symptoms: Noticeable delay when accessing short URLs.
Diagnosis:
# Test redirect time
time curl -I https://go.yourdomain.com/abc12
# Check container resources
docker stats shlink --no-stream Solutions:
- Add Redis caching (see docker-compose addition below)
- Increase database connection pool
- Optimize database indexes
Redis caching configuration:
# Add to docker-compose.yml
services:
redis:
image: redis:7-alpine
container_name: shlink-redis
restart: always
networks:
- shlink-net
shlink:
# Add to environment section
environment:
CACHE_NAMESPACE: shlink
REDIS_SERVERS: redis:6379 Backup and Maintenance
Automated Backup Script
#!/bin/bash
# backup-shlink.sh
BACKUP_DIR="/backups/shlink"
DATE=$(date +%Y%m%d-%H%M%S)
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Backup database
docker exec shlink-db pg_dump -U shlink shlink | gzip > "$BACKUP_DIR/shlink-db-$DATE.sql.gz"
# Backup configuration
cp ~/shlink/docker-compose.yml "$BACKUP_DIR/docker-compose-$DATE.yml"
# Remove backups older than 30 days
find "$BACKUP_DIR" -name "shlink-db-*.sql.gz" -mtime +30 -delete
echo "Backup completed: $DATE" Make executable and schedule:
chmod +x backup-shlink.sh
# Add to crontab (daily at 2 AM)
crontab -e
0 2 * * * /root/backup-shlink.sh >> /var/log/shlink-backup.log 2>&1 Update Shlink
cd ~/shlink
# Pull latest images
docker compose pull
# Restart with new images
docker compose up -d
# Check version
docker logs shlink | grep version Monitor Health
# Health check script
#!/bin/bash
HEALTH=$(curl -s http://localhost:8080/rest/health | jq -r '.status')
if [ "$HEALTH" != "pass" ]; then
echo "Shlink health check failed!" | mail -s "Alert: Shlink Down" [email protected]
fi Conclusion
You now have a production-ready URL shortener with:
- Custom branded short domains
- Comprehensive analytics and tracking
- REST API for automation
- QR code generation
- Browser extensions for quick shortening
- Privacy-focused self-hosted infrastructure
Next steps:
- Configure custom domain for professional short links
- Install browser extension for daily use
- Explore API integrations with your applications
- Set up automated backups
- Create short URLs for social media campaigns
Shlink provides enterprise-level URL shortening capabilities without compromising on privacy or features. Whether you’re tracking marketing campaigns, creating memorable links for documentation, or building URL shortening into your applications, Shlink delivers a robust, modern solution.
Additional Resources
Official:
Tools:
Community:
Last Updated: January 2025 Version Covered: Shlink 3.7.0+