Sentry is an error tracking and performance monitoring platform: its SDKs capture exceptions with stack traces, request context and breadcrumbs, and group them into issues you can assign, resolve and track across releases. Sentry's self-hosted edition runs the same product on your own server with Docker Compose. In this tutorial you will install self-hosted Sentry on Ubuntu 24.04 with the official installer, publish it at https://sentry.your_domain behind Nginx, configure outgoing email, capture an error from a Python script, and register a release with sentry-cli.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS with at least 4 vCPUs, 16 GB of RAM and 40 GB of free SSD space, for example a CubePath VPS. Self-hosted Sentry runs dozens of containers, including Kafka, ClickHouse, PostgreSQL and Redis, and will not install on smaller machines.
  • A non-root user with sudo privileges who is a member of the docker group, and UFW enabled with SSH allowed.
  • Docker Engine and the Docker Compose plugin installed from Docker's official repository, plus git.
  • A domain name with a DNS A record for sentry.your_domain pointing to your_server_ip.
  • SMTP credentials for sending email (your mail provider or a transactional email service).

Step 1 - Adding swap space

Sentry's documentation asks for 16 GB of swap in addition to 16 GB of RAM, because several components spike in memory during migrations and upgrades. Check what you have:

free -h

If the Swap line shows 0B, create a 16 GB swap file and make it permanent:

sudo fallocate -l 16G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Run free -h again and confirm the Swap total is now 16Gi.

Step 2 - Downloading the latest release

The getsentry/self-hosted repository tags a release every month. Always install from a tag, not from the master branch. Find the latest release tag:

VERSION=$(curl -Ls -o /dev/null -w '%{url_effective}' https://github.com/getsentry/self-hosted/releases/latest | sed 's@.*/@@')
echo "$VERSION"
26.9.0

Your value will be whatever the current release is. Clone the repository into /opt/sentry and check out that tag:

sudo mkdir -p /opt/sentry
sudo chown "$USER": /opt/sentry
git clone https://github.com/getsentry/self-hosted.git /opt/sentry
cd /opt/sentry
git checkout "$VERSION"

Step 3 - Running the installer

install.sh checks the minimum requirements, builds the local images, generates a secret key in sentry/config.yml, and runs all database and ClickHouse migrations. Read it before running it, as you would any installation script:

less install.sh

Then run it. Expect it to take 10 to 20 minutes on the first install:

./install.sh

Near the end, the installer asks whether to create a user account. Answer y and enter the email and password for your first superuser. It may also ask whether to send anonymous error reports about the installation itself to Sentry; choose according to your policy.

A successful run ends with a message similar to:

You're all done! Run the following command to get Sentry running:

  docker compose up --wait

If you skipped user creation, you can create the superuser later with docker compose run --rm web createuser.

Step 4 - Binding Sentry to localhost

Sentry's own Nginx container publishes the web interface on port 9000 of every interface. Docker writes its own iptables rules for published ports and bypasses UFW, so restrict the binding itself. Open the .env file in the repository:

nano /opt/sentry/.env

Find SENTRY_BIND and change it so Sentry only listens on localhost:

SENTRY_BIND=127.0.0.1:9000

While the file is open, review SENTRY_EVENT_RETENTION_DAYS. It controls how long events are kept (90 days by default); lowering it is the main lever for disk usage.

Step 5 - Configuring the URL, email and proxy headers

Sentry needs to know its public URL to build links in emails and the DSNs shown to developers. Open the main configuration file:

nano /opt/sentry/sentry/config.yml

Set the URL prefix and the SMTP settings. The keys already exist in the file, some of them commented out; replace the values rather than adding duplicates:

system.url-prefix: 'https://sentry.your_domain'

mail.backend: 'smtp'
mail.host: 'smtp.your_mail_provider.com'
mail.port: 587
mail.username: 'your_smtp_user'
mail.password: 'your_smtp_password'
mail.use-tls: true
mail.use-ssl: false
mail.from: 'sentry@your_domain'

Because TLS will terminate at the host's Nginx, Django inside Sentry must trust the forwarded protocol header, or logins will fail CSRF checks. Open the Python settings file:

nano /opt/sentry/sentry/sentry.conf.py

Find the block about running behind an SSL reverse proxy, uncomment its settings, and add your domain to the trusted origins so the block looks like this:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SOCIAL_AUTH_REDIRECT_IS_HTTPS = True

CSRF_TRUSTED_ORIGINS = ["https://sentry.your_domain"]

Start Sentry. --wait returns only when every container reports healthy:

cd /opt/sentry
docker compose up --wait

Check the health endpoint:

curl -s http://127.0.0.1:9000/_health/
ok

Step 6 - Publishing Sentry with Nginx and HTTPS

Install Nginx and Certbot on the host:

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

Create a server block:

sudo nano /etc/nginx/sites-available/sentry
server {
    listen 80;
    listen [::]:80;
    server_name sentry.your_domain;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:9000;
        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_read_timeout 300s;
    }
}

client_max_body_size lets SDKs upload large events, minidumps and source map bundles, which Nginx would otherwise reject above 1 MB. Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/sentry /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Allow web traffic and request a certificate. Certbot adds the TLS configuration and an HTTP to HTTPS redirect:

sudo ufw allow 'Nginx Full'
sudo certbot --nginx -d sentry.your_domain

Browse to https://sentry.your_domain and log in with the superuser. On first login Sentry shows a short welcome form where you confirm the root URL and the admin email.

To verify email delivery, open Admin from the user menu, go to Mail, and send a test email to yourself. If it fails, the error is shown on the page and in docker compose logs --tail=50 worker.

Step 7 - Creating a project and capturing an error

Go to Projects > Create Project, choose Python, give it a name such as demo-app and create it. Sentry shows the project's DSN, which looks like this:

https://[email protected]_domain/2

You can find it again later under Settings > Projects > demo-app > Client Keys (DSN).

On any machine with Python, create a virtual environment and install the SDK:

sudo apt install python3-venv
python3 -m venv ~/sentry-demo
source ~/sentry-demo/bin/activate
pip install sentry-sdk

Create a small script:

nano ~/sentry-demo/app.py
import sentry_sdk

sentry_sdk.init(
    dsn="https://[email protected]_domain/2",
    environment="staging",
    release="[email protected]",
    traces_sample_rate=1.0,
    send_default_pii=False,
)


def average(values):
    return sum(values) / len(values)


average([])

Replace the DSN with yours and run it:

python ~/sentry-demo/app.py

The script ends with a ZeroDivisionError. The SDK captures unhandled exceptions and flushes them before the interpreter exits. Within a few seconds, Issues shows a ZeroDivisionError: division by zero issue with the stack trace pointing at average, tagged with environment:staging and release:[email protected].

In a real application, lower traces_sample_rate (for example to 0.1) so that only a fraction of requests send performance data. The SDK detects frameworks such as Django, Flask and FastAPI automatically.

Step 8 - Alerting and releases

New projects come with a default alert rule that emails project members the first time a new issue is seen. Review or extend it under Alerts, for example to alert when an issue is seen more than 100 times in one hour in production.

Releases connect errors to the deploy that introduced them. Install sentry-cli from its GitHub releases:

sudo curl -fsSL -o /usr/local/bin/sentry-cli \
  https://github.com/getsentry/sentry-cli/releases/latest/download/sentry-cli-Linux-x86_64
sudo chmod +x /usr/local/bin/sentry-cli
sentry-cli --version

Create an auth token under Settings > Auth Tokens (organization tokens are the right choice for CI). Then point the CLI at your instance and register a release:

export SENTRY_URL=https://sentry.your_domain
export SENTRY_AUTH_TOKEN=your_auth_token
export SENTRY_ORG=your_org_slug
export SENTRY_PROJECT=demo-app

sentry-cli releases new "[email protected]"
sentry-cli releases finalize "[email protected]"
sentry-cli releases list

Run the same commands from your CI pipeline on every deploy, passing the same release string to sentry_sdk.init(release=...), and Sentry will mark issues as new or regressed per release.

Upgrading Sentry

Upgrades use the same installer. Fetch the new tags, check out the next release (26.10.0 in this example) and run ./install.sh again, which applies any new migrations:

cd /opt/sentry
git fetch --tags
git stash
git checkout 26.10.0
git stash pop
./install.sh
docker compose up --wait

git stash keeps your .env change across the checkout. Read the release notes first: some versions are hard stops that you must install before jumping further, and skipping them breaks migrations. Take a snapshot of the server before each upgrade.

Troubleshooting

install.sh stops with a requirements error. It checks CPU count, RAM and Docker versions. Resize the server or update Docker; do not bypass the check, since an undersized server fails later during ingestion.

Login loops back to the login page or shows a CSRF error. The proxy settings in sentry.conf.py are not active or the domain is missing from CSRF_TRUSTED_ORIGINS. Fix them and run docker compose restart web.

The SDK sends events but nothing appears. Events enter through the relay container and are processed asynchronously. Check docker compose logs --tail=100 relay and the consumer containers with docker compose ps for restarts. Memory pressure is the usual cause; check free -h and docker stats --no-stream.

Conclusion

You installed self-hosted Sentry from a tagged release, bound it to localhost behind Nginx with HTTPS, configured email, and captured and grouped your first error with release information. Next, add the SDK to your real services (Sentry has official SDKs for JavaScript, Node.js, Go, PHP, Java and more), invite your team and map them to projects, and schedule the monthly upgrade so the instance stays within supported versions.