Skip to main content

Self-Hosting Dub: URL Shortener on Your Own Domain 2026

·OSSAlt Team
duburl-shortenerself-hostingdockerguide
Share:

Dub is the open source Bitly alternative — short links, QR codes, analytics, and team workspaces. Self-hosting gives you unlimited links, unlimited clicks, custom domains, and full data ownership.

Why Self-Host Dub

Bitly's pricing has shifted dramatically toward enterprise in recent years. The Starter plan at $35/month allows 200 branded links and one custom domain. The Teams plan at $300/month is required for analytics beyond 30 days and multi-user access. Rebrandly, Short.io, and other competitors follow similar pricing tiers that make sense for occasional use but become expensive when links are a core part of your marketing or developer workflows.

Self-hosting Dub on a $12/month VPS gives you unlimited links, unlimited custom domains, and complete click analytics — all for about $144/year. Compare that to Bitly's Teams plan at $3,600/year. For an agency managing links for multiple clients or a SaaS platform using short links in transactional emails, the savings are significant.

Data ownership is critical for URL shorteners specifically. Your short links contain behavioral data — what your users click, when, from where, on what device. This is first-party analytics data that should belong to you. Self-hosting means you own the click stream, you can integrate it with your own analytics pipeline, and there's no risk of your link analytics being used for third-party ad targeting.

Custom domains without limits. SaaS URL shorteners typically charge extra per custom domain or limit the number. Self-hosting Dub lets you run as many short domains as you need — go.yourproduct.com, get.yourproduct.com, on.yourcampaign.com — each routing through the same Dub instance.

When NOT to self-host Dub: Dub is a Next.js application with multiple services (PostgreSQL, Redis, S3-compatible storage) — it's more operationally complex than a single-binary tool. If you need short links for personal use or a tiny team with under 200 links, Bitly Free or Short.io's free tier saves the setup time. Also, Dub Cloud handles global CDN and edge redirects for you; self-hosted redirects route through your single VPS, which can add latency for geographically distributed users.

Prerequisites

Dub requires more services than most self-hosted apps — a database, Redis cache, and object storage all need to be configured correctly before the app runs. Picking the right VPS provider early avoids needing to migrate later.

Server specs: The minimum is 2 GB RAM, but 4 GB is strongly recommended for a production Dub instance. Redis and PostgreSQL each want memory to work efficiently, and the Next.js app itself needs headroom for SSR. A 2 vCPU server handles the Redis and database I/O without being a bottleneck.

Operating system: Ubuntu 22.04 LTS. The Docker Compose setup has been tested on Ubuntu 22.04 and follows standard conventions. Avoid running Dub directly on the host without Docker — the Node.js version requirements can conflict with other applications on the same server.

Two domains: Dub requires a separate domain for the app dashboard and the short link domain. This is by design — the short link domain needs to handle high-throughput redirects without going through the full app interface. Point both DNS records to the same server IP with A records.

S3-compatible storage: Dub stores QR code images and open graph images in object storage. You can use AWS S3, Cloudflare R2 (free egress, excellent for link thumbnails), Backblaze B2, or MinIO if you want fully local storage. R2 is the most cost-effective choice for most deployments.

Skills required: Comfortable with multi-service Docker Compose deployments, environment variable configuration, and basic DNS management. DNS propagation must complete before the short link domain will work.

Requirements

  • VPS with 2 GB RAM minimum
  • Docker and Docker Compose
  • Two domain names: one for the app (e.g., links.yourdomain.com) and one for short links (e.g., go.yourdomain.com)
  • 10+ GB disk

Step 1: Clone and Configure

git clone https://github.com/dubinc/dub.git
cd dub

# Copy environment file
cp .env.example .env

Step 2: Configure Environment

Edit .env:

# App
NEXTAUTH_SECRET=your-random-secret-32-chars
NEXTAUTH_URL=https://links.yourdomain.com

# Database
DATABASE_URL=postgresql://dub:your-strong-password@db:5432/dub

# Redis
UPSTASH_REDIS_REST_URL=http://redis:8079
UPSTASH_REDIS_REST_TOKEN=your-redis-token

# Short link domain
NEXT_PUBLIC_APP_DOMAIN=links.yourdomain.com
SHORT_DOMAIN=go.yourdomain.com

# Storage (for QR codes, OG images)
STORAGE_ACCESS_KEY=your-s3-key
STORAGE_SECRET_KEY=your-s3-secret
STORAGE_ENDPOINT=https://s3.yourdomain.com
STORAGE_BUCKET_NAME=dub

# SMTP
SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=resend
SMTP_PASSWORD=re_your_api_key
EMAIL_FROM=links@yourdomain.com

Generate secrets:

openssl rand -hex 32  # NEXTAUTH_SECRET

Step 3: Docker Compose Setup

# docker-compose.yml
services:
  dub:
    build: .
    container_name: dub
    restart: unless-stopped
    ports:
      - "3000:3000"
    env_file: .env
    depends_on:
      - db
      - redis

  db:
    image: postgres:16-alpine
    container_name: dub-db
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=dub
      - POSTGRES_USER=dub
      - POSTGRES_PASSWORD=your-strong-password

  redis:
    image: redis:7-alpine
    container_name: dub-redis
    restart: unless-stopped
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

Step 4: Build and Start

docker compose build
docker compose up -d

Step 5: Reverse Proxy (Caddy)

# /etc/caddy/Caddyfile

# App dashboard
links.yourdomain.com {
    reverse_proxy localhost:3000
}

# Short link domain
go.yourdomain.com {
    reverse_proxy localhost:3000
}
sudo systemctl restart caddy

Step 6: DNS Configuration

links.yourdomain.com  A  your-server-ip
go.yourdomain.com     A  your-server-ip

Step 7: Initial Setup

  1. Open https://links.yourdomain.com
  2. Create your account
  3. Create your first workspace
  4. Add your custom short link domain (go.yourdomain.com)

Via dashboard:

  1. Click Create Link
  2. Enter destination URL
  3. Customize short path (optional)
  4. Add UTM parameters
  5. Generate QR code
  6. Save

Via API:

curl -X POST 'https://links.yourdomain.com/api/links' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data '{
    "url": "https://example.com/long-page-url",
    "key": "launch",
    "domain": "go.yourdomain.com"
  }'
# Creates: go.yourdomain.com/launch

Bulk create:

curl -X POST 'https://links.yourdomain.com/api/links/bulk' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data '[
    { "url": "https://example.com/page-1", "key": "p1" },
    { "url": "https://example.com/page-2", "key": "p2" },
    { "url": "https://example.com/page-3", "key": "p3" }
  ]'

Step 9: Analytics

Dub tracks for every link:

  • Click count over time
  • Geographic distribution (country, city)
  • Device and browser breakdown
  • Referral sources
  • Top performing links

API analytics:

curl 'https://links.yourdomain.com/api/analytics?domain=go.yourdomain.com&key=launch&interval=30d' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Step 10: Team Workspaces

  1. SettingsTeam → invite members
  2. Assign roles (owner, member)
  3. Team members share links and analytics
  4. Use tags to organize links by campaign

Production Hardening

Backups:

# Database backup (daily cron)
docker exec dub-db pg_dump -U dub dub > /backups/dub-$(date +%Y%m%d).sql

For offsite backup of your PostgreSQL data, set up automated server backups with restic to push snapshots to Backblaze B2 or S3-compatible storage daily. Link data and analytics are in the database — losing this data means losing your click history and link configurations.

Updates:

cd dub
git pull
docker compose build
docker compose up -d

Monitoring:

  • Monitor short link domain with Uptime Kuma
  • Track redirect latency (should be < 100ms)
  • Set up disk space alerts

Resource Usage

LinksRAMCPUDisk
1-10K1 GB1 core5 GB
10K-100K2 GB2 cores10 GB
100K+4 GB4 cores30 GB

VPS Recommendations

ProviderSpecPrice
Hetzner2 vCPU, 4 GB RAM€4.50/month
DigitalOcean2 vCPU, 2 GB RAM$12/month
Linode1 vCPU, 2 GB RAM$12/month

Production Security Hardening

Short link servers are a target because attackers can probe them to discover internal URLs or attempt to poison your link analytics. Follow the full self-hosting security checklist and apply these Dub-specific measures.

UFW firewall rules: Only expose Caddy's ports to the internet. Block direct access to the app port.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 3000/tcp   # Block direct app access
sudo ufw enable

Fail2ban: Protect your SSH port from brute force attacks, especially important since your short link domain will receive public traffic and attract automated scanners.

sudo apt install fail2ban -y

Add /etc/fail2ban/jail.local:

[sshd]
enabled = true
maxretry = 5
bantime = 3600
findtime = 600

Secure your .env file: The .env file contains your NEXTAUTH_SECRET, database password, and storage credentials. Lock down permissions and never commit it:

chmod 600 .env
echo ".env" >> .gitignore

Disable SSH password auth: Once key-based SSH is confirmed working, edit /etc/ssh/sshd_config:

PasswordAuthentication no
PermitRootLogin no

Restart: sudo systemctl restart sshd

Automatic security updates:

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades

Rate limiting links: If you're running Dub publicly, consider adding Caddy rate limiting to prevent your short link server from being used for large-scale scraping or redirect attacks. Caddy's rate_limit directive or Fail2ban watching your access logs can block abusive IPs.

Troubleshooting Common Issues

Build fails during docker compose build

Dub's Next.js build requires significant memory. On a 1 GB RAM server, the build process may OOM-kill. Either temporarily add swap space (fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile) or build on a larger machine and push the image. A 2 GB RAM server builds comfortably.

Short links redirect to wrong domain

Dub routes traffic based on the hostname in the request. If go.yourdomain.com isn't matching, verify that the SHORT_DOMAIN environment variable exactly matches the domain in your Caddy config and DNS. Also confirm that both domains in Caddy are proxying to the same Dub container port.

QR codes and OG images not generating

These features require S3-compatible object storage to be configured and reachable. Check that your STORAGE_ENDPOINT, STORAGE_ACCESS_KEY, and STORAGE_BUCKET_NAME are correct. Test S3 connectivity with: docker exec dub curl -s $STORAGE_ENDPOINT. Public bucket access is needed for Dub to serve QR code images.

Analytics not recording clicks

Redis must be running and reachable. Check docker logs dub-redis for errors and docker exec dub-redis redis-cli ping (should return PONG). Dub uses Redis to buffer click events before writing them to PostgreSQL — if Redis is down, clicks are dropped.

Database migration errors on startup

If you see Prisma migration errors in docker logs dub, the database may have been initialized with a different schema version. Run migrations manually:

docker exec dub npx prisma migrate deploy

If that fails, check that the DATABASE_URL environment variable matches the credentials in your PostgreSQL container.

Ongoing Maintenance and Operations

Self-hosted Dub is relatively low-maintenance once running. Here's what ongoing operations look like in practice.

Link performance monitoring. The most important operational metric for a URL shortener is redirect latency. Your self-hosted Dub instance routes all redirects through your single VPS — compared to Bitly's global CDN, there may be latency for users geographically distant from your server. Hetzner has datacenters in Nuremberg, Helsinki, Ashburn (US East), and Hillsboro (US West) — pick the location closest to your primary user base. Monitor redirect latency from multiple regions using UptimeRobot's multi-location monitoring.

Redis health. Dub uses Redis to cache link destinations and buffer analytics events. If Redis runs out of memory or restarts, short link redirects may temporarily slow down while the cache warms up. Monitor Redis memory usage: docker exec dub-redis redis-cli info memory | grep used_memory_human. Redis's default maxmemory-policy noeviction will refuse writes when memory is full — set a policy appropriate for your use case.

Analytics data accumulation. Click analytics accumulate in PostgreSQL over time. For high-traffic links receiving millions of clicks, the LinkClick table grows large and can slow down analytics queries. Consider setting up a data retention policy: archive or delete click data older than 12 months to keep the database size manageable. The analytics UI becomes sluggish when the click table exceeds a few hundred million rows on a modest server.

Updating Dub. Since you've cloned the Dub repository and built from source, updates require pulling the latest code and rebuilding the Docker image. Check the Dub changelog before updating — Dub is under active development and occasionally introduces environment variable changes or database migrations that require attention.

API key rotation. If you share the Dub API with multiple team members or external tools, create per-user API keys in SettingsAPI Keys rather than sharing a single key. This allows you to revoke individual keys without disrupting other integrations. Rotate API keys quarterly as a security best practice.

Backup verification. Monthly, verify that your database backups are actually restorable. A backup you've never tested isn't a real backup. Pick a test environment, restore the latest pg_dump, and confirm that link configurations and analytics data are present. The whole process should take under 30 minutes and gives you confidence that your backup strategy actually works.

Domain management. If you add new short link domains over time, each must be added to both your DNS settings (A record pointing to the server) and Caddy configuration (reverse_proxy block). Caddy handles SSL automatically for any new domain you add to the Caddyfile. Restart Caddy after adding a domain: sudo systemctl restart caddy. New domains appear in the Dub workspace once DNS resolves.

UTM parameter management. Dub's UTM builder lets you standardize UTM parameters across all marketing campaigns. Define a UTM convention for your team (e.g., utm_source=email&utm_medium=newsletter&utm_campaign=april-launch) and use Dub's built-in UTM templates to apply them consistently when creating links. Consistent UTM parameters are critical for accurate attribution in Google Analytics — inconsistent naming (email vs Email vs e-mail) fragments attribution data and understates the value of specific channels. Dub's UTM templates enforce naming conventions across your entire team automatically.


Compare link management tools on OSSAlt — features, analytics, and pricing side by side.

See open source alternatives to Dub on OSSAlt.

The SaaS-to-Self-Hosted Migration Guide (Free PDF)

Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.

Join 300+ self-hosters. Unsubscribe in one click.