Caption: One HTTPS hostname for the app, /admin, and /backend — collections stay on my Postgres, not in a vendor workspace.
Why I wanted this on my server
Staging URLs, auth headers, and half-real payloads do not belong in a personal Postman cloud. I wanted a browser API client on my VPS: REST, GraphQL, environments, shared collections, login I control.
Hoppscotch Community Edition does that. Self-hosting is not “a prettier curl.” It is where those collections live, who can open them, and whether I can restore Postgres after a bad night.
What I actually installed
Ubuntu 24.04, Docker Compose, the official all-in-one Hoppscotch image, Postgres 17, a one-shot Prisma migrate container, Nginx. Subpath mode so one domain covers:
https://hoppscotch.example.comhttps://hoppscotch.example.com/adminhttps://hoppscotch.example.com/backend
AIO binds to 127.0.0.1:3080. 1–2 GB RAM is enough for a small team. SSD for Postgres.
Where it broke
On a fresh Ubuntu 24.04 box this install is famous for OAuth callback mismatches.
The UI loaded. Admin opened. GitHub login came back invalid redirect_uri. I had registered https://hoppscotch.example.com/auth/github/callback like a normal app. Hoppscotch in subpath mode wants the backend path:
GITHUB_CALLBACK_URL=https://hoppscotch.example.com/backend/v1/auth/github/callback
GITHUB_SCOPE=user:email
Scheme, host, and path have to match the GitHub OAuth App exactly. After I fixed the callback, login worked.
If the app boots with a missing migration, I inspect the migrate job and re-run:
docker compose run --rm --entrypoint sh hoppscotch -c "pnpm exec prisma migrate deploy"
docker compose logs hoppscotch-migrate
Caption: Nginx on 443. AIO, migrate job, and Postgres stay private.
Prerequisites
Docker Engine + Compose v2, Nginx, Certbot, an OAuth app if the team logs in through GitHub/Google/Microsoft. Official env files want no quotes and no spaces around =. DATA_ENCRYPTION_KEY is 32 characters; lose it and stored secrets in the DB are junk.
sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg git ufw nginx certbot python3-certbot-nginx openssl
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
The working install
1. Install Docker Engine
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
newgrp docker
docker --version
docker compose version
2. Create the project directory
sudo mkdir -p /opt/hoppscotch/backups
sudo chown -R "$USER":"$USER" /opt/hoppscotch
chmod 700 /opt/hoppscotch/backups
cd /opt/hoppscotch
3. Generate secrets and create .env
openssl rand -hex 16 is 32 hex characters — that matches Hoppscotch’s encryption key length.
cd /opt/hoppscotch
POSTGRES_PASSWORD_VALUE="$(openssl rand -base64 36 | tr -d '\n')"
DATA_ENCRYPTION_KEY_VALUE="$(openssl rand -hex 16)"
cat > .env <<EOF
POSTGRES_USER=hoppscotch
POSTGRES_PASSWORD=${POSTGRES_PASSWORD_VALUE}
POSTGRES_DB=hoppscotch
DATABASE_URL=postgresql://hoppscotch:${POSTGRES_PASSWORD_VALUE}@hoppscotch-db:5432/hoppscotch?connect_timeout=300
HOPP_AIO_ALTERNATE_PORT=80
DATA_ENCRYPTION_KEY=${DATA_ENCRYPTION_KEY_VALUE}
WHITELISTED_ORIGINS=https://hoppscotch.example.com,app://localhost_3200,app://hoppscotch
VITE_BASE_URL=https://hoppscotch.example.com
VITE_SHORTCODE_BASE_URL=https://hoppscotch.example.com
VITE_ADMIN_URL=https://hoppscotch.example.com/admin
VITE_BACKEND_GQL_URL=https://hoppscotch.example.com/backend/graphql
VITE_BACKEND_WS_URL=wss://hoppscotch.example.com/backend/graphql
VITE_BACKEND_API_URL=https://hoppscotch.example.com/backend/v1
VITE_APP_TOS_LINK=https://hoppscotch.example.com/terms
VITE_APP_PRIVACY_POLICY_LINK=https://hoppscotch.example.com/privacy
ENABLE_SUBPATH_BASED_ACCESS=true
EOF
chmod 600 .env
nano .env
If you split app, admin, and backend across hostnames, every VITE_* URL and WHITELISTED_ORIGINS has to match those hostnames. I stayed on one domain.
4. Create the Docker Compose file
services:
hoppscotch:
image: hoppscotch/hoppscotch:latest
restart: unless-stopped
env_file: ./.env
depends_on:
hoppscotch-db:
condition: service_healthy
hoppscotch-migrate:
condition: service_completed_successfully
ports:
- "127.0.0.1:3080:80"
healthcheck:
test: ["CMD-SHELL", "wget -q --tries=1 --spider http://localhost || exit 1"]
interval: 30s
timeout: 5s
retries: 5
start_period: 45s
hoppscotch-migrate:
image: hoppscotch/hoppscotch:latest
env_file: ./.env
depends_on:
hoppscotch-db:
condition: service_healthy
entrypoint: ["/bin/sh", "-c"]
command: "pnpm exec prisma migrate deploy"
restart: "no"
hoppscotch-db:
image: postgres:17
restart: unless-stopped
user: postgres
env_file: ./.env
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
Community Edition can also run as separate frontend/backend/admin containers. AIO is enough for a first private box. Pin tags after a known-good boot.
5. Start Hoppscotch
cd /opt/hoppscotch
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --since=2m hoppscotch-migrate hoppscotch
The migrate service must finish pnpm exec prisma migrate deploy before AIO stays up. DATABASE_URL uses the Compose hostname hoppscotch-db, and that password must match POSTGRES_PASSWORD.
6. Configure Nginx and HTTPS
Create /etc/nginx/sites-available/hoppscotch.example.com:
server {
listen 80;
listen [::]:80;
server_name hoppscotch.example.com;
client_max_body_size 50M;
location / {
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_pass http://127.0.0.1:3080;
}
}
Upgrade and Connection matter. GraphQL over WebSocket uses VITE_BACKEND_WS_URL with wss:// on HTTPS. Without those headers, the page loads and API calls die quietly.
sudo ln -s /etc/nginx/sites-available/hoppscotch.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d hoppscotch.example.com
Keep in mind before deploy
If the UI loads but GraphQL and REST from the app fail, the VITE_BACKEND_* URLs are usually still localhost or missing /backend. Subpath mode is not optional once you chose it: app, admin, and API URLs have to agree.
Caption: Callback URL first, then one test account, then anyone else.
I configure SMTP from admin or env only after login works. Some VPS providers block outbound 25; use the relay they document.
First workspace I actually used
Health checks and unauthenticated /version routes first. Dev and staging environments with fake tokens. One REST request, one GraphQL query. Production secrets stay out until I have proven OAuth, backups, and who is admin.
Restart the stack once and confirm collections survive. If a restore comes back empty, I dumped the wrong Compose project or database name.
Backup, expose, next step
cd /opt/hoppscotch
mkdir -p backups
BACKUP_DATE="$(date +%F-%H%M%S)"
docker compose exec -T hoppscotch-db pg_dump -U hoppscotch hoppscotch > "backups/hoppscotch-postgres-${BACKUP_DATE}.sql"
tar czf "backups/hoppscotch-config-${BACKUP_DATE}.tar.gz" compose.yaml .env
sha256sum backups/hoppscotch-*-"${BACKUP_DATE}".* > "backups/hoppscotch-${BACKUP_DATE}.sha256"
rsync -avh /opt/hoppscotch/backups/ backup-user@backup.example.com:/srv/backups/hoppscotch/
Restore on a spare host: same .env (especially DATA_ENCRYPTION_KEY), import Postgres, start Compose, GitHub login, admin, collections.
Caption: Dump, Compose, encryption key, checksum, off-server copy. The key is not optional.
What I have running now is Hoppscotch on one HTTPS hostname, AIO + Postgres, GitHub OAuth with the backend callback. Next I add a small internal collection convention and a restore drill before this becomes the team’s source of API examples.
Did you hit the same wall?
I got stuck on GitHub invalid redirect_uri until the callback was https://hoppscotch.example.com/backend/v1/auth/github/callback. Did you hit the same thing, or a different one? Tell me in the comments. I read them.
Need this done on your server?
I deploy and harden Laravel/CodeCanyon apps on cPanel or VPS, and offer monthly Server Watch retainers. Hire for deploy · Care plan