URL Shortener Guide

    Deploying Shlink

    Shlink is a powerful, open-source, self-hosted URL shortener built with PHP. Create branded short links under your own domain, track click analytics with geolocation data, and manage everything through a REST API, CLI, or web dashboard on RamNode's reliable VPS hosting.

    Branded Short Links
    Click Analytics
    Geolocation
    REST API
    1

    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
    2

    Prerequisites

    RamNode VPS Requirements

    ResourceRecommendation
    PlanKVM VPS — 2 GB RAM / 1 vCPU (starting at $4/month)
    OSUbuntu 22.04 LTS or 24.04 LTS
    Storage15 GB SSD minimum (included in base plan)
    DomainA registered domain with DNS pointed to your VPS IP
    DNS RecordsA 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.

    3

    Provision Your RamNode VPS

    SSH into your VPS
    ssh root@YOUR_VPS_IP
    Create a non-root user
    adduser deploy
    usermod -aG sudo deploy
    su - deploy
    Update system packages
    sudo apt update && sudo apt upgrade -y
    4

    Install Docker and Docker Compose

    Install Docker
    curl -fsSL https://get.docker.com | sudo sh
    sudo usermod -aG docker $USER
    newgrp docker
    Verify installation
    docker --version
    docker compose version
    5

    Configure DNS

    Point your short domain to your RamNode VPS. Create the following DNS records:

    TypeNameValueTTL
    As.example.comYOUR_VPS_IP300
    Aadmin.example.comYOUR_VPS_IP300

    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.

    6

    Create the Environment File

    Create project directory and .env file
    mkdir -p ~/shlink && cd ~/shlink
    nano ~/shlink/.env
    ~/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_York
    Generate strong passwords
    openssl rand -base64 32

    Security Note: Never commit your .env file to version control. Add it to .gitignore if you track your configuration in Git.

    7

    Create the Docker Compose File

    ~/shlink/docker-compose.yml
    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: bridge

    Architecture 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.

    8

    Deploy the Stack

    Start all containers
    cd ~/shlink
    docker compose up -d
    Verify containers
    docker compose ps

    You 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.

    Test the Shlink API locally
    curl -I http://127.0.0.1:8080

    A 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.

    9

    Configure Nginx Reverse Proxy with SSL

    Install Nginx and Certbot
    sudo apt install nginx certbot python3-certbot-nginx -y
    /etc/nginx/sites-available/shlink
    server {
        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)

    /etc/nginx/sites-available/shlink-admin
    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;
        }
    }
    Enable sites and obtain SSL
    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.com

    Web 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.

    10

    Create Your First Short URL

    Using the CLI

    CLI short URL management
    # 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 promo2025

    Using the REST API

    API short URL creation
    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.

    11

    Ongoing Maintenance

    12

    Troubleshooting

    IssueSolution
    Container won't startCheck logs with docker logs shlink_app. Verify .env values are correct and the database container is running.
    502 Bad GatewayShlink container may still be starting. Wait 30 seconds and retry. Verify with docker compose ps.
    SSL certificate errorEnsure DNS A records are propagated (check with dig s.example.com). Re-run certbot if needed.
    GeoLite2 download failsVerify your GEOLITE_LICENSE_KEY is correct. Keys can be regenerated from your MaxMind dashboard.
    Database connection refusedConfirm DB_HOST matches the database service name (shlink_db). Check that the DB container is healthy.
    Short URLs return 404Verify 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.