Workload · Running your own error tracking

Self hosted Sentry, set up on a server that is yours

You can run Sentry yourself by cloning the official self-hosted repository onto a Linux server and running its installer, which builds the whole thing with Docker Compose. Plan on 4 cores or more, 16 GB of memory with an equal amount of swap behind it, and 20 GB of disk space to spare, and aim for the 32 GB of memory Sentry's own team recommends. This guide keeps the dashboard private until your admin account exists, then gives it an HTTPS address.

The short version
  1. You own the data; there is no paid tier
  2. Give it 32 GB of memory and fast disks
  3. Private until your admin account exists
  4. Upgrade monthly, one hard stop at a time
  5. Nightly copy of accounts, projects and config

Is running it yourself a good idea?

It is when you want error reports to stay on hardware you control and you are happy to look after an upgrade once a month. Sentry describes self-hosted as roughly its Business plan with no software limits and nothing to pay, but a few things never leave its cloud: the Seer AI features, spike protection, spend allocation, and it has limited iOS symbolication and PlayStation crash support.

Be clear about support too. The docs say Sentry's engineers help on a best-effort basis and otherwise point you to the community Discord, and they offer no advice on scaling past one machine. If you need a vendor to call, the hosted product is the honest answer.

How much computer does it take?

More than people expect, because one Sentry install is really a small data centre in a box: its docker-compose.yml lists over 50 services, from the web app and Relay to Kafka, ClickHouse, Snuba, PostgreSQL and Redis. Here are the published numbers:

  • Processor: at least 4 cores. On x86-64 it needs SSE 4.2 for ClickHouse, and install/check-minimum-requirements.sh checks that for you.
  • Memory: 16 GB plus 16 GB of swap is the floor, 32 GB the recommendation. The installer refuses to continue if Docker can see less than 14,000 MB.
  • Disk: 20 GB free to start, and speed matters more than size. The docs read iowait above roughly 10% of CPU time as a sign the disk cannot keep up.
  • Software: Docker 19.03.6 or later and Compose 2.32.2 or later, ideally on Debian or Ubuntu. RHEL-family systems have known install problems and Alpine is not supported.

Short on memory? Since 24.8.0 there is an errors-only mode (COMPOSE_PROFILES=errors-only) that keeps issues, alerts and releases but drops tracing, profiling, session replay, cron monitoring and user feedback. In that mode install/_min-requirements.sh relaxes the installer's check to 7,000 MB and two CPUs.

The server that fits

Pick memory first and fast storage second. On our dedicated servers page these are the good matches:

  • A Xeon D-1587 cartridge: 16 cores, a 1.92 TB NVMe drive and up to 128 GB of memory. Choose 32 GB or more.
  • Bucharest's Xeon E-2286M, an 8-core chip with 32 or 64 GB of memory, and two NVMe SSDs of 960 GB each.
  • The two-processor servers, for example Xeon Gold 6130 or Xeon E5-2630L v3: between 16 and 32 cores, room for far more memory, plus an out-of-band console, included with rack servers and blades, so a bad firewall change never leaves you blind.

Leave out the small 8 GB cartridges and anything with spinning disks. This guide sticks to the x86-64 machines, so set the ARM servers (X-Gene and Ampere Altra) aside. A ready server is yours within 30 minutes of approval. Nobody meters the port, so a chatty app that floods you with events never turns into a bandwidth bill. You manage the server; we keep the hardware and the network running.

Step 1: install it where only you can reach it

Before you start, add Docker Engine and its Compose plugin using Docker's guide for Ubuntu. Sentry's own instructions are short: clone the newest release tag, run ./install.sh, then start the stack with docker compose up --wait. The block follows them, and adds four things of ours, explained below it.

# Paste into a root shell (sudo -i). Needs Ubuntu 24.04 plus Docker Engine 28 or newer
# and its Compose plugin, both from Docker's apt repo. Leave t unset for the newest
# release, and change e= near the end to your email. Turns the firewall on first;
# stops if /opt/sentry is already there. Ends by asking for the admin password.
v=$(docker version --format '{{.Server.Version}}')
if [ "${v%%.*}" -ge 28 ] 2>/dev/null && [ ! -e /opt/sentry ]; then
  sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw &&
  ufw allow OpenSSH && ufw --force enable &&
  if [ -z "${t:-}" ]; then
    t=$(curl -Ls -o /dev/null -w '%{url_effective}' https://github.com/getsentry/self-hosted/releases/latest)
    t=${t##*/}
  fi &&
  git clone https://github.com/getsentry/self-hosted.git /opt/sentry &&
  cd /opt/sentry && git checkout "$t" &&
  s=$(openssl rand -hex 32) && [ ${#s} -eq 64 ] &&
  install -m 600 /dev/null .env.custom &&
  printf 'SENTRY_BIND=127.0.0.1:9000\nLAUNCHPAD_RPC_SHARED_SECRET=%s\n' "$s" > .env.custom &&
  ./install.sh --skip-user-creation --no-report-self-hosted-issues &&
  docker compose --env-file .env --env-file .env.custom up --wait &&
  curl -fsS http://127.0.0.1:9000/_health/ && echo " - up on 127.0.0.1:9000" &&
  e=you@example.com && cd /opt/sentry &&
  docker compose --env-file .env --env-file .env.custom run --rm web createuser --email "$e" --superuser
else
  echo "stopped: Docker '$v' is older than 28, or /opt/sentry already exists"
fi
  • Firewall first. ufw comes on before a single container listens, letting in SSH alone, over IPv4 and IPv6.
  • A private port. Out of the box .env publishes Sentry on port 9000 on every address, IPv4 and IPv6, and on IPv4 Docker's rules route around ufw. The new .env.custom file pins it to 127.0.0.1 instead, which keeps it private only from Docker Engine release 28 onward, hence the version test at the top.
  • A fresh shared secret. The same file replaces the LAUNCHPAD_RPC_SHARED_SECRET default, which the configuration docs call too weak for production.
  • No prompts halfway. Two installer flags skip the account question and the question about sending diagnostics to Sentry; the account is made at the very end instead, where it asks you for a password.

One habit to keep: when .env.custom exists, the docs say .env is ignored, so give compose both files every time, .env first, exactly as the block does.

Step 2: log in privately and finish the welcome screen

  1. On your own computer, open a tunnel through your normal SSH login: ssh -L 19000:127.0.0.1:9000 you@SERVER_IP.
  2. Browse to http://localhost:19000 and log in using the address and password you gave in step 1.
  3. When the welcome screen asks for the root URL, type the HTTPS address you will use later, such as https://sentry.example.com.
  4. Before you close the tunnel, add two-factor authentication to your account.

Step 3: a real address with HTTPS

Sentry asks for its own load balancer or proxy in front, handling the certificate. Once one is there, two files need to know about it: the SSL/TLS section of sentry/sentry.conf.py, plus system.url-prefix inside sentry/config.yml, or you can run into CSRF errors on some actions, configuring integrations among them. This block edits both, re-runs the installer as the docs suggest after config changes, then sets up Caddy, which fetches the certificate for you.

# Root shell, after step 2. Set h to your hostname (DNS pointing here).
h=sentry.example.com; k='SECURE_PROXY_SSL_HEADER|USE_X_FORWARDED_HOST|SESSION_COOKIE_SECURE|CSRF_COOKIE_SECURE|SOCIAL_AUTH_REDIRECT_IS_HTTPS'; cd /opt/sentry &&
sed -i "s|^# system.url-prefix:.*|system.url-prefix: 'https://$h'|" sentry/config.yml &&
grep -qx "system.url-prefix: 'https://$h'" sentry/config.yml &&
sed -i -E "s/^# ($k) /\\1 /" sentry/sentry.conf.py &&
[ "$(grep -cE "^($k) " sentry/sentry.conf.py)" = 5 ] &&
sed -i "s|^# CSRF_TRUSTED_ORIGINS = .*|CSRF_TRUSTED_ORIGINS = [\"https://$h\"]|" sentry/sentry.conf.py &&
grep -q "^CSRF_TRUSTED_ORIGINS = \[\"https://$h\"\]" sentry/sentry.conf.py &&
./install.sh --skip-user-creation --no-report-self-hosted-issues && docker compose --env-file .env --env-file .env.custom up --wait &&
apt-get install -y caddy && cat > /etc/caddy/Caddyfile <<EOF &&
$h {
  @ingest_endpoint path_regexp ^/api/[0-9]+/(envelope|minidump|security|store|unreal)/
  handle @ingest_endpoint {
    header {
      Access-Control-Allow-Origin *
      Access-Control-Allow-Credentials false
      Access-Control-Allow-Methods GET,POST,PUT
      Access-Control-Allow-Headers sentry-trace,baggage
      Access-Control-Expose-Headers sentry-trace,headers
    }
    reverse_proxy 127.0.0.1:9000
  }
  handle {
    reverse_proxy 127.0.0.1:9000
  }
}
EOF
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile &&
ufw allow 80,443/tcp && systemctl reload caddy && echo "live: https://$h"

What we changed from the official material: we also fill in CSRF_TRUSTED_ORIGINS, a setting the example config file describes for installs behind a proxy. Our Caddyfile is Sentry's Caddy example cut down. We swapped in your hostname and 127.0.0.1:9000, and removed the comments, the tls email line, the block that deletes the Server header, and from both proxy sections the health_uri, health_status and header_up Host lines. Caddy itself runs outside Docker, so ufw gets a rule for 80 and 443 here.

Nothing in self-hosted Sentry limits how fast events arrive, so the docs recommend a rate limiter at the proxy if the instance is public; for Caddy that means adding the caddy-ratelimit module. Apps outside your office only need the ingest paths, and the docs' Reverse Proxy page lists them if you would rather keep the dashboard internal.

Keeping a copy of your setup

Sentry's supported backup exports accounts, organisations, projects, teams, alert rules and global settings as JSON, but not the events themselves. The script below runs that export every night with the reporting question switched off, because a question nobody can answer under cron makes the backup fail. Because scripts/backup.sh leaves its file readable and writable by anyone, the script copies it, at mode 600, to a folder only root can open, next to the release tag and a tarball of your config.

# Root shell. Writes a backup script and a cron entry (03:30 every night), then runs
# it once. Files live 14 days in /var/backups/sentry; copying them elsewhere is up to you.
install -m 700 /dev/null /usr/local/sbin/sentry-backup.sh &&
cat > /usr/local/sbin/sentry-backup.sh <<'EOF' &&
set -euo pipefail
umask 077
d=/var/backups/sentry; t=$(date +%F)
mkdir -p -m 700 "$d"
cd /opt/sentry
./scripts/backup.sh global --no-report-self-hosted-issues
python3 -m json.tool sentry/backup.json > /dev/null
install -m 600 sentry/backup.json "$d/sentry-export-$t.json" && rm sentry/backup.json
git describe --tags > "$d/sentry-release-$t.txt"
tar -czf "$d/sentry-config-$t.tar.gz" -C / opt/sentry/.env.custom \
  opt/sentry/sentry/config.yml opt/sentry/sentry/sentry.conf.py etc/caddy/Caddyfile
tar -tzf "$d/sentry-config-$t.tar.gz" > /dev/null
find "$d" -name 'sentry-*' -mtime +14 -delete
find /opt/sentry -maxdepth 1 -name 'sentry_backup_log-*' -mtime +14 -delete
echo "backup ok $t"
EOF
printf '30 3 * * * root bash /usr/local/sbin/sentry-backup.sh > /var/log/sentry-backup.log 2>&1\n' > /etc/cron.d/sentry-backup &&
bash /usr/local/sbin/sentry-backup.sh

To keep event history as well, you would have to copy the Docker volumes the installer creates (sentry-postgres, sentry-clickhouse, sentry-kafka, sentry-data, sentry-redis, sentry-seaweedfs, per install/create-docker-volumes.sh) with the stack stopped. Sentry calls that unsupported and at your own risk.

Bringing a backup back replaces what is in the affected tables, so Sentry advises an empty install of the very same version. The safest way is a second server, with the old one left running in case you need to go back:

  1. Copy one night's three files (export, release file, config tarball) into /root on the replacement machine, and note the tag inside the release file.
  2. In a root shell, set t to that tag, paste the step 1 block, and stop at the password prompt with Ctrl+C: the restore brings your accounts back.
  3. Unpack the config tarball over / with --exclude=etc/caddy/Caddyfile (step 3 writes that file again, and an existing copy would make the caddy package pause to ask about it), copy the export to /opt/sentry/sentry/backup.json with mode 644, and from /opt/sentry run REPORT_SELF_HOSTED_ISSUES=0 ./scripts/restore.sh global. A clean run exits with code 0.
  4. Delete that backup.json, run the installer and the up --wait line once more (the restore script stops every container when it finishes), move DNS to the new server, then run step 3.

Staying current, and the fine print

A new release lands each month, numbered by date, with the main one on the 15th, and older versions are never patched. To upgrade, take a backup first. Then, in /opt/sentry, fetch and switch to the release you want, compare the example files under sentry/ with your own, then run the installer and up --wait again. The installer stops every service while migrations run, so plan for downtime. Jumping several releases at once means stepping through each hard stop on the Releases page (lately 25.5.1, 26.5.0 and 26.7.0) and avoiding the versions it marks as broken.

Old events are deleted after 90 days by default; to change that, add SENTRY_EVENT_RETENTION_DAYS with a new number to .env.custom, then run the installer again. The bundled mail container works for trying things out, but mailbox providers often flag or drop what it sends, so the docs recommend sending through a mail provider by filling in the mail.* options of sentry/config.yml. To switch off the beacon, which fetches version news and system notices and sends anonymous usage numbers, add the line SENTRY_BEACON = False to sentry/sentry.conf.py; if the server should make no outside calls whatsoever, the docs point to SENTRY_AIR_GAP = True there instead.

About the licence, in our general reading and not as legal advice: Sentry uses the Functional Source License (LICENSE.md names FSL-1.1-Apache-2.0). You can run and change it for your own team or company. You cannot sell a running copy to others as a service or build a competitor from the code. Each release turns into Apache 2.0 two years later. For more on running databases on your own box, see our PostgreSQL workload guide and ClickHouse workload guide.

Questions people ask

Is 16 GB of memory really enough?
It is the minimum Sentry publishes, but only alongside 16 GB of swap, ideally on a fast disk. The recommendation is 32 GB, and that is what we would pick. The installer itself stops below 14,000 MB visible to Docker.
Why does my server need SSE 4.2?
ClickHouse, one of the databases inside Sentry, requires that instruction set on x86-64 processors. The installer checks for it and stops if it is missing. Every machine we suggest above is an Intel Xeon on x86-64.
Can I skip releases when upgrading?
Only up to the next hard stop. Sentry lists versions you must pass through in order, plus a few to avoid, on its Releases page. Upgrading every month keeps the jump small.
Will my backups bring back old errors?
No. The supported export restores people, projects, teams, alerts and settings, not events. Copying the Docker volumes can keep events, but Sentry does not support that method.
Do I need an account with Sentry to self-host?
No. You clone the public repository and run the installer; there is no licence key and no paid tier. The instance can still check in with sentry.io for version news unless you turn the beacon off.

Ready to host your own Sentry?

Pick 32 GB of memory or more and NVMe or SSD storage, then follow the three steps.

Get a server for Sentry

Read next