Skip to main content

Self-Hosting Guide

Deploy LinkForty Core on your own infrastructure with complete control over your data.

What is LinkForty Core?

This guide covers deploying LinkForty Core - the open-source deeplink engine. Core provides a REST API for link management, device-specific redirects, click analytics, deferred deep linking, QR codes, and webhooks. It does not include the Cloud dashboard, team management, or billing features. For a managed experience with a UI, see the Quick Start for LinkForty Cloud.

What You Get

LinkForty Core is a Fastify server backed by PostgreSQL (with optional Redis caching) that provides:

  • REST API for creating, reading, updating, and deleting links
  • Smart redirect routing with device detection (iOS, Android, web)
  • Click analytics with geolocation, device type, and UTM tracking
  • Deferred deep linking and fingerprint-based attribution
  • QR code generation (PNG and SVG)
  • Webhook notifications for real-time events
  • Social previews via Open Graph tags
  • iOS Universal Links and Android App Links support (.well-known endpoints)

All interactions with Core happen via its API. There is no built-in UI - you integrate it into your own application or use it as a standalone API service.


Why Self-Host?

Control and Flexibility:

  • No per-install fees - unlimited scaling
  • No vendor lock-in
  • Deploy anywhere (AWS, GCP, Azure, DigitalOcean, on-premise)

Data Privacy:

  • Complete data ownership
  • GDPR/CCPA compliance on your terms
  • No third-party data sharing

Customization:

  • Full source code access (AGPL-3.0 license)
  • Custom features and integrations
  • Modify attribution logic for your use case

System Requirements

Minimum Specifications

Server:

  • CPU: 2 cores
  • RAM: 4 GB
  • Storage: 20 GB SSD
  • OS: Ubuntu 20.04+ / Debian 11+ / RHEL 8+

Software:

  • Docker 20.10+ and Docker Compose 2.0+
  • OR Node.js 18+ with PostgreSQL 14+ and Redis 7+ (optional)

Expected Load Capacity:

  • ~1,000 requests/minute
  • ~100,000 links
  • ~1M clicks/month

Server:

  • CPU: 4 cores
  • RAM: 8 GB
  • Storage: 100 GB SSD
  • OS: Ubuntu 22.04 LTS

Expected Load Capacity:

  • ~10,000 requests/minute
  • ~1M links
  • ~10M clicks/month

Cloud Provider Options

Recommended instance types for minimum specifications:

ProviderInstance TypeSpecs
DigitalOceanDroplet (Basic)2 CPU, 4GB RAM
AWSt3.medium2 CPU, 4GB RAM
Google Cloude2-medium2 CPU, 4GB RAM
HetznerCX212 CPU, 4GB RAM
VultrRegular Performance2 CPU, 4GB RAM

Installation Methods

Choose your deployment method:

  • Method 1: Docker Compose (Recommended) - Fastest and easiest. Everything configured out of the box.
  • Method 2: npm Package - Install Core as an npm dependency in your own Node.js application.
  • Method 3: Manual Installation - For custom setups or when Docker isn't available.
  • Method 4: Kubernetes - For enterprise deployments with high availability.

Step 1: Download Docker Compose File

mkdir linkforty && cd linkforty
curl -O https://raw.githubusercontent.com/linkforty/core/main/docker-compose.yml

Step 2: Configure Environment

Create a .env file:

# Database
POSTGRES_DB=linkforty
POSTGRES_USER=linkforty
POSTGRES_PASSWORD=your-strong-password
POSTGRES_PORT=5432

# Redis (optional but recommended for caching)
REDIS_URL=redis://redis:6379
REDIS_PORT=6379

# Server
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
LINKFORTY_PORT=3000

# CORS - set to your frontend domain
CORS_ORIGIN=*

# Optional: behind a CDN/proxy (e.g. Cloudflare), read the real client IP
# from the proxy's header so attribution stays accurate. Only enable when
# the origin is reachable ONLY through that proxy. See the env-var reference.
# TRUSTED_CLIENT_IP_HEADER=cf-connecting-ip

# Optional: JWT secret for authentication
# JWT_SECRET=your-super-secret-jwt-key

# Optional: Custom domain for short links
# SERVICE_DOMAIN=yourdomain.com

# Optional: iOS Universal Links
# IOS_TEAM_ID=ABC123XYZ
# IOS_BUNDLE_ID=com.yourcompany.yourapp

# Optional: Android App Links
# ANDROID_PACKAGE_NAME=com.yourcompany.yourapp
# ANDROID_SHA256_FINGERPRINTS=AA:BB:CC:DD:...

Security Note: Generate strong secrets with:

openssl rand -hex 32

Step 3: Start Services

docker compose up -d

This starts three containers:

  • linkforty: The Core Fastify API server (port 3000)
  • postgres: PostgreSQL database (port 5432)
  • redis: Redis cache (port 6379)

The database schema is automatically initialized on first startup.

Step 4: Verify Installation

# Check all services are running
docker compose ps

# Test the API
curl http://localhost:3000/health

You should see a healthy response. The API is now available at http://localhost:3000.

curl -X POST http://localhost:3000/api/links \
-H "Content-Type: application/json" \
-d '{
"userId": "user-1",
"originalUrl": "https://example.com",
"title": "My First Link",
"iosUrl": "https://apps.apple.com/app/your-app/id123456789",
"androidUrl": "https://play.google.com/store/apps/details?id=com.yourapp",
"webFallbackUrl": "https://example.com",
"utmParameters": {
"source": "twitter",
"medium": "social",
"campaign": "launch"
}
}'

The response will include a short_code you can use for redirects: http://localhost:3000/{shortCode}

Step 6: Set Up SSL (Required for Production)

Option A: Using Let's Encrypt (Free)

Install Certbot and Nginx:

sudo apt-get update
sudo apt-get install certbot python3-certbot-nginx nginx

Configure Nginx as a reverse proxy:

# /etc/nginx/sites-available/linkforty
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name yourdomain.com;

ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

# API and redirect routes
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
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;
proxy_set_header Host $host;
}
}

Enable site and generate certificate:

sudo ln -s /etc/nginx/sites-available/linkforty /etc/nginx/sites-enabled/
sudo certbot --nginx -d yourdomain.com
sudo systemctl reload nginx

Option B: Using Cloudflare (Free SSL + CDN)

  1. Point your domain to Cloudflare nameservers
  2. Enable Full (strict) SSL mode in Cloudflare dashboard
  3. Create an origin certificate in Cloudflare
  4. Install the origin certificate on your server
  5. Enable Always Use HTTPS in Cloudflare

Method 2: npm Package

Install LinkForty Core as a dependency in your own Node.js application:

npm install @linkforty/core

Basic Server

import { createServer } from '@linkforty/core';

async function start() {
const server = await createServer({
database: {
url: 'postgresql://linkforty:password@localhost:5432/linkforty',
},
redis: {
url: 'redis://localhost:6379',
},
});

await server.listen({ port: 3000, host: '0.0.0.0' });
console.log('LinkForty Core running on http://localhost:3000');
}

start();

Using Individual Route Handlers

You can also register only specific routes in your own Fastify application:

import Fastify from 'fastify';
import { initializeDatabase, redirectRoutes, linkRoutes, analyticsRoutes } from '@linkforty/core';

const fastify = Fastify();

// Initialize database separately
await initializeDatabase({ url: 'postgresql://localhost/linkforty' });

// Register only the routes you need
await fastify.register(redirectRoutes);
await fastify.register(linkRoutes);
await fastify.register(analyticsRoutes);

await fastify.listen({ port: 3000 });

This gives you maximum flexibility to integrate LinkForty into an existing application.


Method 3: Manual Installation

Step 1: Install Dependencies

# Update system
sudo apt-get update && sudo apt-get upgrade -y

# Install Node.js 18+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

# Install PostgreSQL 14+
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y postgresql-14

# Install Redis 7+ (optional but recommended)
sudo apt-get install -y redis-server

Step 2: Configure PostgreSQL

sudo -u postgres psql << EOF
CREATE DATABASE linkforty;
CREATE USER linkforty WITH PASSWORD 'your-strong-password';
GRANT ALL PRIVILEGES ON DATABASE linkforty TO linkforty;
\q
EOF

Step 3: Clone and Build

git clone https://github.com/linkforty/core.git
cd core
npm install
npm run build

Step 4: Configure Environment

cp .env.example .env
nano .env

Update the environment variables:

DATABASE_URL=postgresql://linkforty:your-strong-password@localhost:5432/linkforty
REDIS_URL=redis://localhost:6379
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
CORS_ORIGIN=*

Step 5: Run Database Migrations

npm run migrate

Step 6: Set Up systemd Service

Create a service file for automatic startup:

sudo nano /etc/systemd/system/linkforty.service
[Unit]
Description=LinkForty Core
After=network.target postgresql.service redis.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/linkforty/core
Environment="NODE_ENV=production"
EnvironmentFile=/opt/linkforty/core/.env
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable linkforty
sudo systemctl start linkforty

# Check status
sudo systemctl status linkforty

Method 4: Kubernetes

Prerequisites

  • Kubernetes cluster (EKS, GKE, AKS, or self-hosted)
  • kubectl configured
  • Helm 3+

Step 1: Add Helm Repository

helm repo add linkforty https://charts.linkforty.com
helm repo update

Step 2: Create Values File

# values.yaml
replicaCount: 3

image:
repository: linkforty/core
tag: "latest"
pullPolicy: IfNotPresent

service:
type: LoadBalancer
port: 80

ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: linkforty.yourdomain.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: linkforty-tls
hosts:
- linkforty.yourdomain.com

postgresql:
enabled: true
auth:
username: linkforty
password: your-strong-password
database: linkforty
primary:
persistence:
size: 20Gi

redis:
enabled: true
auth:
enabled: false
master:
persistence:
size: 8Gi

env:
NODE_ENV: production
CORS_ORIGIN: "*"

resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi

autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 80

Step 3: Install with Helm

helm install linkforty linkforty/core -f values.yaml

Step 4: Verify Deployment

kubectl get pods
kubectl get services
kubectl logs -l app=linkforty

Post-Installation Setup

1. Configure Custom Domain

Update DNS records to point to your server:

A    yourdomain.com     → your-server-ip

Then set the SERVICE_DOMAIN environment variable:

SERVICE_DOMAIN=yourdomain.com

For iOS Universal Links and Android App Links, set the appropriate environment variables:

# iOS Universal Links
IOS_TEAM_ID=ABC123XYZ
IOS_BUNDLE_ID=com.yourcompany.yourapp

# Android App Links
ANDROID_PACKAGE_NAME=com.yourcompany.yourapp
ANDROID_SHA256_FINGERPRINTS=AA:BB:CC:DD:...

Core will automatically serve the .well-known/apple-app-site-association and .well-known/assetlinks.json files. See iOS Universal Links and Android App Links for the full setup.

3. Preserve Client IP Behind a CDN

If you run LinkForty behind Cloudflare, a CDN, or a load balancer, the connection IP Core sees is the proxy's — not the visitor's. Since attribution fingerprinting keys on the client IP, this hurts match accuracy. Point Core at your proxy's authoritative client-IP header:

TRUSTED_CLIENT_IP_HEADER=cf-connecting-ip   # Cloudflare

Only enable this when your origin is reachable exclusively through that proxy — otherwise the header can be spoofed. See the Environment Variables reference for details.

4. Set Up Backups

PostgreSQL Backup Script

#!/bin/bash
# /opt/linkforty/backup.sh

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/linkforty"
mkdir -p $BACKUP_DIR

# Backup database
docker compose exec -T postgres pg_dump -U linkforty linkforty | gzip > "$BACKUP_DIR/linkforty_$DATE.sql.gz"

# Keep only last 30 days
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete

# Upload to S3 (optional)
# aws s3 cp "$BACKUP_DIR/linkforty_$DATE.sql.gz" s3://your-backup-bucket/

Add to crontab:

crontab -e

# Daily backup at 2 AM
0 2 * * * /opt/linkforty/backup.sh

5. Set Up Monitoring

Using Docker Stats

docker stats linkforty-linkforty-1 linkforty-postgres-1

Using Prometheus + Grafana

# Add to docker-compose.yml
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"

grafana:
image: grafana/grafana
ports:
- "3002:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin

6. Configure Firewall

# Allow HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Allow SSH
sudo ufw allow 22/tcp

# Enable firewall
sudo ufw enable

Updating LinkForty

Docker Compose

cd /opt/linkforty

# Pull latest image
docker compose pull

# Restart with new image
docker compose up -d

Manual Installation

cd /opt/linkforty/core

# Pull latest code
git pull origin main

# Update dependencies
npm install

# Rebuild
npm run build

# Run migrations (if needed)
npm run migrate

# Restart service
sudo systemctl restart linkforty

Troubleshooting

Server Won't Start

# Check logs (Docker)
docker compose logs linkforty

# Check logs (systemd)
sudo journalctl -u linkforty -f

# Common issues:
# 1. Database not ready - wait 10 seconds and retry
# 2. Redis not accessible - check REDIS_URL (Redis is optional, remove REDIS_URL to skip)
# 3. Port 3000 in use - change PORT in .env

Database Connection Errors

# Test PostgreSQL connection (Docker)
docker compose exec postgres psql -U linkforty -d linkforty -c "SELECT 1;"

# Check DATABASE_URL format:
# postgresql://username:password@host:port/database

High Memory Usage

# Increase PostgreSQL shared_buffers
docker compose exec postgres psql -U linkforty -d linkforty -c "ALTER SYSTEM SET shared_buffers = '256MB';"
docker compose restart postgres

# Increase Node.js memory limit
# Add to environment in docker-compose.yml:
# NODE_OPTIONS: "--max-old-space-size=2048"

Redis Connection Errors

Redis is optional. If you don't need caching, remove the REDIS_URL environment variable and Core will fall back to direct database queries. If you do use Redis and see connection errors:

# Check Redis is running
docker compose exec redis redis-cli ping
# Should return: PONG

Production Checklist

Before going live:

  • SSL certificate installed and working
  • Strong database password set (not the default changeme)
  • Database backups configured (daily minimum)
  • Firewall rules configured
  • Monitoring set up
  • DNS records pointing to server
  • Rate limiting configured (if needed)
  • Test link created and redirect working
  • Health check endpoint responding (/health)
  • CORS configured for your frontend domain

API Reference

Once Core is running, all interactions happen through the REST API:

MethodEndpointDescription
POST/api/linksCreate a new link
GET/api/links?userId=...List all links for a user
GET/api/links/:id?userId=...Get a specific link
PUT/api/links/:id?userId=...Update a link
DELETE/api/links/:id?userId=...Delete a link
GET/api/analytics/overview?userId=...Analytics overview
GET/api/analytics/links/:id?userId=...Link-specific analytics
GET/:shortCodeRedirect (public, no auth)

For full API documentation, see the API Reference.


Getting Help

Next Steps