How to Deploy PocketBase on a VPS
Updated Sep 2026
Self-host PocketBase on your own VPS — a single Go binary with embedded SQLite, built-in auth, realtime subscriptions, and an admin dashboard, running as a systemd service behind HTTPS.
- A VPS with 512 MB+ RAM (PocketBase's own minimum — a 1 GB tier gives comfortable headroom for the OS and a reverse proxy)
- A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
- A domain you can point at the server — the admin dashboard shouldn't be run over plain HTTP
What PocketBase is
PocketBase is an open-source backend that ships as a single Go binary: an embedded SQLite database, built-in authentication, realtime subscriptions, file storage, and an admin dashboard, with zero external services to run alongside it. There's no separate database container, no cache, no queue — the binary you download is the backend. It's MIT-licensed, rated 1 / 5 to deploy, and happily starts on 512 MB of RAM.
That "one file" pitch is the whole appeal for self-hosting: you're not
operating a database server, just a process and a data directory. This guide
covers getting that process onto a VPS the right way — running as a system
service (not a foreground ./pocketbase serve in a terminal you'll
eventually disconnect from), behind HTTPS, with real backups and an upgrade
path. For help deciding which VPS and tier to put it on, see
Best VPS for PocketBase — this guide picks up
from "you have a server" and covers the install itself.
Server sizing
PocketBase's own documented minimum is 512 MB RAM, and because SQLite is
embedded in the binary, there's no separate database process competing for
that memory — the number you see in htop for the pocketbase process is
close to the whole footprint. Add a small reverse proxy (Caddy, covered
below) and the OS itself, and a 1 GB VPS has real headroom to spare for a
personal project or small team.
Disk, not RAM, is what to watch as usage grows. Everything PocketBase
owns — the SQLite database file, uploaded files, and backup archives — lives
under one pb_data directory on the same volume as the binary. A hobby
project barely touches it; an app that accepts file uploads from users can
grow that directory steadily. Pick a plan with NVMe/SSD storage you can
expand later rather than over-provisioning RAM PocketBase won't use.
Prepare the server
Start from a fresh Ubuntu 24.04 or 26.04 server and create a non-root user:
apt update && apt -y upgrade
adduser deploy
usermod -aG sudo deploy
Log back in as deploy for the rest of this guide, then set up a basic
firewall — SSH plus the two ports the reverse proxy will need:
ssh deploy@your-server-ip
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
PocketBase itself needs no port open to the internet — it will listen only
on 127.0.0.1, and the reverse proxy is the sole public door (same pattern
as every other app on this site).
Paid link — we earn a commission if you shop through it.
Install PocketBase
Grab the current release directly from GitHub rather than hardcoding a version number that will go stale — this resolves whatever the latest tagged release is at the time you run it:
sudo apt-get update && sudo apt-get install -y unzip curl
PB_VERSION=$(curl -fsSL https://api.github.com/repos/pocketbase/pocketbase/releases/latest \
| grep '"tag_name"' | cut -d '"' -f4)
echo "Installing PocketBase ${PB_VERSION}"
sudo mkdir -p /opt/pocketbase
curl -LO "https://github.com/pocketbase/pocketbase/releases/download/${PB_VERSION}/pocketbase_${PB_VERSION#v}_linux_amd64.zip"
sudo unzip -o "pocketbase_${PB_VERSION#v}_linux_amd64.zip" -d /opt/pocketbase
rm "pocketbase_${PB_VERSION#v}_linux_amd64.zip"
sudo chown -R deploy:deploy /opt/pocketbase
That unpacks a single pocketbase executable into /opt/pocketbase. Running
it directly confirms the binary works before you wire it into systemd:
/opt/pocketbase/pocketbase --version
Run it as a systemd service, not a terminal session that dies on
disconnect. Bind to loopback only — --http=127.0.0.1:8090 — because Caddy
is about to become the only thing the internet can reach:
sudo tee /etc/systemd/system/pocketbase.service > /dev/null <<'EOF'
[Unit]
Description=PocketBase
After=network.target
[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/opt/pocketbase
ExecStart=/opt/pocketbase/pocketbase serve --http=127.0.0.1:8090
Restart=always
RestartSec=5
LimitNOFILE=4096
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now pocketbase
sudo systemctl status pocketbase
WorkingDirectory=/opt/pocketbase is what determines where the pb_data
directory (database, uploads, logs) gets created — pass --dir explicitly
instead if you want it somewhere else.
HTTPS + domain
Create an A record for your subdomain (e.g. pb.example.com) pointing
at the server's public IP, and confirm it resolves before requesting a
certificate:
dig +short pb.example.com
Then put Automatic HTTPS with Caddy in front of the loopback address PocketBase is already listening on — the whole config is one block:
pb.example.com {
reverse_proxy 127.0.0.1:8090
}
Because PocketBase sits behind a proxy, it sees every request coming from
127.0.0.1 unless you tell it otherwise. Set the trusted proxy headers under
Dashboard → Settings → Application (or the equivalent pocketbase.io
docs section on reverse proxies) so rate limiting, logs, and auth rules see
the real client IP from X-Forwarded-For rather than the proxy's own
address.
First superuser and hardening
Create the first admin account from the command line rather than exposing an open signup page to the internet — PocketBase has no public registration for superusers by default, but setting it explicitly up front avoids ever depending on the dashboard's first-run flow being reachable before anything else is:
sudo -u deploy /opt/pocketbase/pocketbase superuser create you@example.com 'a-long-random-password'
Then, before opening the app up to real users:
- Load
https://pb.example.com/_/and confirm you can sign in as that superuser — this is the admin dashboard, separate from your app's own collections and API. - Set collection-level API rules deliberately. PocketBase collections are locked down by default (empty rule = admin-only); every collection you expose to your app's users needs its own list/view/create/update/delete rules reviewed, not left on whatever the scaffold generated.
- Turn on the encrypted-settings env var (
--encryptionEnv=SOME_VAR, set as anEnvironment=line in the systemd unit) if you store OAuth secrets or SMTP credentials in PocketBase's own settings, so they aren't sitting inpb_data/data.dbin plaintext. - Keep
/opt/pocketbaseandpb_dataowned by thedeployuser only — the systemd unit already runs as that user, not root; don't widen the permissions to fix an unrelated problem.
Backups
Everything PocketBase owns lives under one pb_data directory, which makes
backup strategy simple: back that directory up, and you have the database,
uploaded files, and migrations in one piece.
Built-in backups (simplest). From Dashboard → Settings → Backups,
PocketBase can produce a ZIP snapshot of pb_data on demand or on a
schedule, and ship it to local disk or an S3-compatible bucket. For a
personal or small-team instance this is enough on its own — point it at
object storage so the backup survives the VPS itself dying.
Manual backup (scriptable, cron-friendly). Stop the service first so the SQLite file isn't mid-write when you copy it:
sudo systemctl stop pocketbase
tar -czf "pocketbase-backup-$(date +%F).tar.gz" -C /opt/pocketbase pb_data
sudo systemctl start pocketbase
Move the archive off the box — object storage, another machine, your laptop
— and restore it once into a throwaway instance so you find out now, not
during an outage, that the archive is actually complete: unzip it into a
fresh pb_data, point a test binary at it with --dir, and confirm your
data is there.
Upgrades
PocketBase upgrades are a binary swap, not a package manager operation:
sudo systemctl stop pocketbase
PB_VERSION=$(curl -fsSL https://api.github.com/repos/pocketbase/pocketbase/releases/latest \
| grep '"tag_name"' | cut -d '"' -f4)
curl -LO "https://github.com/pocketbase/pocketbase/releases/download/${PB_VERSION}/pocketbase_${PB_VERSION#v}_linux_amd64.zip"
sudo unzip -o "pocketbase_${PB_VERSION#v}_linux_amd64.zip" -d /opt/pocketbase
rm "pocketbase_${PB_VERSION#v}_linux_amd64.zip"
sudo chown -R deploy:deploy /opt/pocketbase
sudo systemctl start pocketbase
Take a pb_data backup first (previous section) — PocketBase applies
database migrations automatically on startup, and those are one-way. Skim
the release notes
before jumping a minor version; a single-maintainer project moves fast and
occasionally changes a default.
Troubleshooting
systemctl status pocketbase shows the service repeatedly restarting.
Run the ExecStart command by hand as the deploy user
(/opt/pocketbase/pocketbase serve --http=127.0.0.1:8090) to see the actual
error instead of systemd's exit code — a bad --dir permission or a port
already in use are the usual causes.
The dashboard loads over HTTP but Caddy never gets a certificate. Confirm
dig +short pb.example.com actually resolves to the server, and that your
cloud provider's network-level firewall (not just ufw on the box) allows
inbound 80 and 443 — a provider firewall blocking port 80 silently breaks
the ACME challenge with nothing in Caddy's own logs to explain why.
Rate limiting or audit logs show the proxy's IP instead of real clients.
The trusted-proxy header setting from the HTTPS section above wasn't applied
— every request is arriving from 127.0.0.1 as far as PocketBase can tell
until you configure it to trust X-Forwarded-For from the proxy.
A restore comes back with the app running but files or auth broken. The
restore only copied part of pb_data. Database, uploads, and migrations all
live in that one directory — a partial copy (say, database only) is not a
complete restore.
Verification + next steps
You're done when you can: load https://pb.example.com/_/ with a valid
certificate, sign in as the superuser you created from the CLI, confirm
systemctl is-enabled pocketbase reports enabled (it survives a reboot),
create a collection with API rules you set deliberately rather than left on
defaults, and restore a backup archive you've actually tested.
From there, the natural next steps are wiring up the JS or Dart SDK from your frontend, configuring OAuth2 providers under Settings → Auth providers if you need social login, and turning on the built-in email service (or your own SMTP) so account verification and password-reset mail actually sends. For the ranked VPS picks for this workload, see Best VPS for PocketBase.