Surveys
    PostgreSQL

    Deploy Formbricks on a VPS

    Self-host Formbricks surveys on a RamNode VPS with PostgreSQL, Valkey, Docker Compose, Nginx HTTPS, and backups.

    Formbricks is an open-source survey platform for link, embedded website and in-app surveys. This guide runs it with PostgreSQL (pgvector), Valkey/Redis-compatible cache, Nginx and HTTPS on one VPS.

    Prerequisites

    ItemMinimumRecommended
    VPS2 vCPU, 2 GB RAM, 30 GB NVMe2–4 vCPU, 4 GB RAM, 50 GB NVMe
    OSUbuntu 24.04 LTSUbuntu 26.04 LTS
    DNSA record for surveys.example.comAAAA record too, if IPv6 is configured
    SMTPOptionalRecommended for invites, resets and verification

    Deploy the VPS, point DNS to its IP and confirm dig +short surveys.example.com resolves before requesting TLS. Replace the example domain and admin@example.com below. Commands assume root (su - if needed).

    Step 1: Prepare the server

    shell
    ssh root@YOUR_VPS_IP
    apt update && apt -y upgrade
    apt -y install curl ca-certificates openssl ufw nginx certbot python3-certbot-nginx
    hostnamectl set-hostname surveys
    timedatectl set-timezone America/New_York

    On a 2 GB plan, add swap for startup and migrations:

    shell
    fallocate -l 2G /swapfile
    chmod 600 /swapfile
    mkswap /swapfile
    swapon /swapfile
    echo '/swapfile none swap sw 0 0' >> /etc/fstab

    If your SSH port is not 22, allow the correct port before enabling the firewall.

    shell
    ufw allow OpenSSH
    ufw allow 'Nginx Full'
    ufw --force enable
    ufw status

    Step 2: Install Docker

    shell
    curl -fsSL https://get.docker.com | sh
    systemctl enable --now docker
    docker --version
    docker compose version

    Docker-published ports can bypass UFW. Only the app port is bound to 127.0.0.1 below; the database and cache are not published.

    Step 3: Configure the stack

    shell
    mkdir -p /opt/formbricks
    cd /opt/formbricks
    cat > .env <<EOF_ENV
    FORMBRICKS_VERSION=latest
    WEBAPP_URL=https://surveys.example.com
    NEXTAUTH_URL=https://surveys.example.com
    NEXTAUTH_SECRET=$(openssl rand -hex 32)
    ENCRYPTION_KEY=$(openssl rand -hex 32)
    CRON_SECRET=$(openssl rand -hex 32)
    POSTGRES_PASSWORD=$(openssl rand -hex 24)
    EMAIL_VERIFICATION_DISABLED=1
    PASSWORD_RESET_DISABLED=1
    #MAIL_FROM=surveys@example.com
    #SMTP_HOST=smtp.example.com
    #SMTP_PORT=587
    #SMTP_USER=surveys@example.com
    #SMTP_PASSWORD=change-me
    #SMTP_SECURE_ENABLED=0
    EOF_ENV
    chmod 600 .env

    Keep ENCRYPTION_KEY safe with the database backups. Without the original key, encrypted data cannot be read after a restore. When SMTP is configured, remove the two _DISABLED lines and add your mail settings. Pin FORMBRICKS_VERSION to a tested tag from Formbricks releases rather than latest in production.

    Create /opt/formbricks/docker-compose.yml:

    shell
    services:
      postgres:
        image: pgvector/pgvector:pg17
        restart: unless-stopped
        environment:
          POSTGRES_USER: formbricks
          POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
          POSTGRES_DB: formbricks
        volumes:
          - postgres:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U formbricks"]
          interval: 10s
          timeout: 5s
          retries: 5
    
      redis:
        image: valkey/valkey:8-alpine
        restart: unless-stopped
        command: valkey-server --appendonly yes
        volumes:
          - redis:/data
    
      formbricks:
        image: ghcr.io/formbricks/formbricks:${FORMBRICKS_VERSION}
        restart: unless-stopped
        depends_on:
          postgres:
            condition: service_healthy
          redis:
            condition: service_started
        env_file: .env
        environment:
          DATABASE_URL: postgresql://formbricks:${POSTGRES_PASSWORD}@postgres:5432/formbricks?schema=public
          REDIS_URL: redis://redis:6379
        ports:
          - "127.0.0.1:3000:3000"
        volumes:
          - uploads:/home/nextjs/apps/web/uploads
    
    volumes:
      postgres:
      redis:
      uploads:

    For external uploads, Formbricks also supports S3-compatible storage through its S3_* environment settings.

    Step 4: Start Formbricks

    shell
    cd /opt/formbricks
    docker compose pull
    docker compose up -d
    docker compose logs -f formbricks

    Startup migrations can take a couple of minutes. Press Ctrl+C once ready, then check curl -sI http://127.0.0.1:3000 | head -n 1 for a 200 or redirect response.

    Step 5: Configure Nginx and HTTPS

    Create /etc/nginx/sites-available/formbricks:

    shell
    server {
        listen 80;
        listen [::]:80;
        server_name surveys.example.com;
        client_max_body_size 50M;
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_http_version 1.1;
            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;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 300s;
        }
    }
    shell
    ln -s /etc/nginx/sites-available/formbricks /etc/nginx/sites-enabled/
    rm -f /etc/nginx/sites-enabled/default
    nginx -t && systemctl reload nginx
    certbot --nginx -d surveys.example.com --redirect -m admin@example.com --agree-tos -n
    certbot renew --dry-run

    Step 6: Create the owner account

    Open https://surveys.example.com and register with a strong password. The first account owns the first organization; complete onboarding to create a project. Once your team has joined, close public registration by adding SIGNUP_DISABLED=1 to /opt/formbricks/.env and run cd /opt/formbricks && docker compose up -d. Invitations through Organization settings > Members require SMTP.

    Backups and restore

    Back up the PostgreSQL database, uploads and .env (including ENCRYPTION_KEY). Create /usr/local/bin/formbricks-backup:

    shell
    #!/bin/bash
    set -euo pipefail
    DEST=/var/backups/formbricks
    STAMP=$(date +%F-%H%M)
    mkdir -p "$DEST"
    cd /opt/formbricks
    docker compose exec -T postgres pg_dump -U formbricks formbricks | gzip > "$DEST/db-$STAMP.sql.gz"
    docker run --rm -v formbricks_uploads:/data -v "$DEST":/backup alpine \
      tar czf "/backup/uploads-$STAMP.tar.gz" -C /data .
    cp .env "$DEST/env-$STAMP"
    chmod 600 "$DEST"/*
    find "$DEST" -type f -mtime +14 -delete
    shell
    chmod 700 /usr/local/bin/formbricks-backup
    /usr/local/bin/formbricks-backup && ls -lh /var/backups/formbricks
    echo '15 3 * * * root /usr/local/bin/formbricks-backup' > /etc/cron.d/formbricks-backup

    Copy backups off the VPS. To restore a database dump with the original .env:

    shell
    cd /opt/formbricks
    docker compose stop formbricks
    gunzip -c /var/backups/formbricks/db-STAMP.sql.gz | docker compose exec -T postgres psql -U formbricks formbricks
    docker compose start formbricks

    Restore the matching uploads archive too if recovering the full installation.

    Updating Formbricks

    Read release notes, take a backup and change FORMBRICKS_VERSION in .env:

    shell
    /usr/local/bin/formbricks-backup
    cd /opt/formbricks
    docker compose pull
    docker compose up -d
    docker compose logs -f formbricks
    docker image prune -f

    Migrations run on startup. Keep the OS patched with apt update && apt -y upgrade.

    Troubleshooting

    SymptomCheck
    502 Bad GatewayRun docker compose ps and docker compose logs formbricks.
    Database errors on restartCheck the database health and that POSTGRES_PASSWORD has not changed since volume creation.
    Exit 137Increase memory or add swap.
    Login redirects to localhostSet both WEBAPP_URL and NEXTAUTH_URL to your HTTPS domain, then recreate the app.
    Certificate challenge failsCheck DNS, port 80, UFW and Nginx.
    Survey uploads failCheck client_max_body_size and the uploads volume or S3 settings.
    Invitations or resets do not sendConfigure SMTP, remove the _DISABLED settings and recreate the app.