NetBox as a network source of truth

When VLAN numbers or prefix ownership are scattered across files, automation has no clear inventory to trust. NetBox records devices, IP addresses, prefixes, VLANs and their relationships in one source. This single-server tutorial continues through web, API, worker and recovery checks rather than stopping at a working login page.

Baseline versions and hostname

The baseline is NetBox 4.7.2 on Ubuntu 24.04 LTS with Python 3.12, PostgreSQL 16 and Redis 7. Check plugins and that release’s requirements before execution.

Four vCPUs, 8 GiB RAM and 60 GiB SSD are example starting allocations. Measure database/media growth and job load. This single-node design does not implement HA.

Prepare sudo, actual DNS, a trusted certificate and dedicated PostgreSQL identity. Replace netbox.example.com in configuration and Nginx. Generate new SECRET_KEY and pepper values for this installation.

Release-specific configuration

This configuration targets 4.7.2. Match DATABASES, API_TOKEN_PEPPERS and token behavior to that release. This procedure uses Git. Python-package installation is experimental in 4.7.

Web service, database and worker

The user reaches Nginx over HTTPS, and Nginx forwards the request to Gunicorn on loopback. NetBox stores data in PostgreSQL while its worker processes background jobs using Redis. Tasks and cache use separate Redis databases in this example. That path explains why a backup must preserve the database, media, configuration and secrets together.

Example: inventory for three datacenters

The network-owner group maintains devices/prefixes. Automation reads with restricted tokens, while defined roles control writes. Test with one site/prefix and confirm a read-only identity’s write request is rejected.

API permissions and secret protection

Keep PostgreSQL/Redis off public interfaces. Restrict API tokens by role and expiry, and avoid daily superuser use. Retain SECRET_KEY/API_TOKEN_PEPPERS in encrypted backups. Unplanned rotation affects credentials and sessions.

Install dependencies and start services

Use a fresh VM with local DB. Install dependencies and record versions first. This does not perform an automatic full OS upgrade.

BASH
sudo apt-get update
sudo apt-get install -y postgresql redis-server python3 python3-venv python3-dev build-essential libxml2-dev libxslt1-dev libffi-dev libpq-dev libssl-dev zlib1g-dev git nginx
python3 --version
psql --version
redis-cli ping
sudo -u postgres createuser netbox
sudo -u postgres psql -c '\password netbox'
sudo -u postgres createdb -O netbox netbox
sudo -u postgres psql -d netbox -c 'GRANT CREATE ON SCHEMA public TO netbox;'
sudo git clone --branch v4.7.2 --depth 1 https://github.com/netbox-community/netbox.git /opt/netbox
sudo adduser --system --group netbox
sudo chown -R netbox:netbox /opt/netbox/netbox/media /opt/netbox/netbox/reports /opt/netbox/netbox/scripts

The password command prompts interactively. Reuse that value in configuration. Createuser/createdb are for a fresh installation only. Keep Redis on loopback. Generate the complete configuration with Python, entering the DB password at its prompt and replacing the sample hostname first.

BASH
sudo python3 - <<'PY'
from pathlib import Path
import getpass, secrets
db_password = getpass.getpass('PostgreSQL netbox password: ')
host = 'netbox.example.com'  # Replace with the real DNS hostname before running.
if not host or '/' in host or '*' in host:
    raise SystemExit('Use one explicit DNS hostname')
config = {
    'ALLOWED_HOSTS': [host],
    'DATABASES': {'default': {'NAME':'netbox','USER':'netbox','PASSWORD':db_password,'HOST':'127.0.0.1','PORT':'5432','CONN_MAX_AGE':300}},
    'REDIS': {'tasks': {'HOST':'127.0.0.1','PORT':6379,'DATABASE':0,'SSL':False}, 'caching': {'HOST':'127.0.0.1','PORT':6379,'DATABASE':1,'SSL':False}},
    'SECRET_KEY': secrets.token_urlsafe(64),
    'API_TOKEN_PEPPERS': {1:secrets.token_urlsafe(64)},
    'CSRF_TRUSTED_ORIGINS': ['https://' + host],
}
path = Path('/opt/netbox/netbox/netbox/configuration.py')
if path.exists():
    raise SystemExit('Configuration already exists; review it')
path.write_text('\n'.join(f'{k} = {v!r}' for k,v in config.items())+'\n')
PY
sudo chown root:netbox /opt/netbox/netbox/netbox/configuration.py
sudo chmod 640 /opt/netbox/netbox/netbox/configuration.py
sudo /opt/netbox/upgrade.sh
sudo -u netbox /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py check
sudo -u netbox /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py createsuperuser
sudo cp /opt/netbox/contrib/gunicorn.py /opt/netbox/gunicorn.py
sudo cp /opt/netbox/contrib/netbox.service /opt/netbox/contrib/netbox-rq.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now netbox netbox-rq

Before putting Nginx in front of the application, check should pass and the services should be active. Compare the Gunicorn file with ss output; the contrib example usually listens on 127.0.0.1:8001. Write the following server block with the actual hostname and certificate, pointing the static alias at the same checkout.

NGINX
server {
    listen 443 ssl;
    server_name netbox.example.com;
    ssl_certificate /etc/nginx/tls/fullchain.pem;
    ssl_certificate_key /etc/nginx/tls/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    client_max_body_size 25m;
    location /static/ { alias /opt/netbox/netbox/static/; }
    location / {
        proxy_pass http://127.0.0.1:8001;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Save as sites-available/netbox, create the initial symlink, run nginx -t and reload. Certificates must exist before testing. Test curl --fail https://netbox.example.com/login/, login and API reads with a restricted token. Create a sample site/prefix in UI and verify read-only write denial.

Activate the HTTPS virtual host and verify static assets

After creating the original complete TLS server block and approved certificate files, use this fresh-instance procedure. The first command deliberately stops if the configuration file is absent. Do not overwrite an existing symlink or remove Ubuntu’s default site without first checking which services it serves. These are host configuration changes requiring sudo.

BASH
test -f /etc/nginx/sites-available/netbox || exit 1
sudo ln -s /etc/nginx/sites-available/netbox /etc/nginx/sites-enabled/netbox
sudo nginx -t
# Reload only after the syntax test succeeds.
sudo systemctl reload nginx
curl --fail --show-error --head https://netbox.example.com/login/

Expected: successful Nginx syntax and a successful login-page HTTP response with a trusted hostname certificate. File exists on ln means inspect the existing destination, not force replacement. A loaded page with missing styles needs checking the exact /static/ alias and generated static files; it does not imply a database failure.

Healthy UI with stalled jobs

Probe login and API reads through the real path. Monitor netbox-rq, queue age, database load, disks and certificates separately. Active Nginx does not prove jobs execute. Restrict metrics to monitoring networks.

502, CSRF and queue failures

Trace each symptom to its component: for 502, inspect Gunicorn binding and logs; for DisallowedHost, check the actual ALLOWED_HOSTS; for CSRF 403, inspect Origin and X-Forwarded-Proto. If pages work but jobs stall, investigate Redis tasks and netbox-rq rather than Nginx alone.

BASH
systemctl is-active postgresql redis-server netbox netbox-rq nginx
sudo journalctl -u netbox -u netbox-rq -n 100 --no-pager
sudo ss -lntp
redis-cli ping
sudo -u netbox /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py check

Keep secret-bearing configuration out of public tickets. After upgrades, inspect plugins, release notes and migration logs. Deleting migration history is not an upgrade repair.

Coordinated backup and isolated restore

Daily backups target the example 24-hour RPO. Coordinate database/media by stopping relevant writers and jobs during backup. Design PostgreSQL PITR separately for a shorter RPO.

BASH
sudo bash <<'BASH'
set -euo pipefail
umask 077
install -d -m 700 /var/backups/netbox
# Fresh demo paths only: never overwrite the previous recovery set.
test ! -e /var/backups/netbox/netbox.dump
test ! -e /var/backups/netbox/application-state.tar.gz
# Preserve backup failure and also fail if the writers cannot restart.
resume_writers() {
    local backup_status=$?
    trap - EXIT
    if ! systemctl start netbox netbox-rq; then
        echo 'NetBox writers failed to restart; console recovery required' >&2
        exit 1
    fi
    exit "$backup_status"
}
trap resume_writers EXIT
systemctl stop netbox netbox-rq
if systemctl is-active --quiet netbox netbox-rq; then
    echo 'A NetBox writer is still active; backup aborted' >&2
    exit 1
fi
sudo -u postgres pg_dump -Fc netbox > /var/backups/netbox/netbox.dump
tar -czf /var/backups/netbox/application-state.tar.gz -C /opt/netbox netbox/media netbox/reports netbox/scripts netbox/netbox/configuration.py
pg_restore --list /var/backups/netbox/netbox.dump >/dev/null
tar -tzf /var/backups/netbox/application-state.tar.gz >/dev/null
BASH

The example redirects within a root-authorized shell. Copy files off-host to protected storage. Restore on an isolated server with matching version/plugins.

BASH
sudo -u postgres createdb -O netbox netbox_restore
sudo bash -c 'sudo -u postgres pg_restore --no-owner --role=netbox -d netbox_restore < /var/backups/netbox/netbox.dump'
sudo -u postgres psql -d netbox_restore -c 'SELECT count(*) FROM dcim_site;'

In the isolated environment, point the database configuration at netbox_restore and restore media, secrets and pepper. Keep outbound webhooks and automation disabled so tests cannot act on real devices. Check login, object counts, the API and a test job, recording recovery time. After a migration, reverting code alone is not a complete recovery procedure.

The archive also retains custom reports and scripts. Keep the restored environment from sending production jobs to real devices.

Data ownership and upgrades

NetBox is not an NMS or automatic discovery tool. Assign inventory owners and update procedures. Pin versions/plugins and test migrations in staging with a restorable backup.

Actually stop writers during this single-node backup

The backup block above implements a coordinated single-node pause: announce downtime, let jobs finish, then stop netbox and netbox-rq before both archives are collected. Its exit trap restarts those services after success or failure. Check service-start status and the original failing endpoint afterward. Fresh-path guards prevent silently replacing a previous recovery set; for recurring backups use a reviewed unique-set/retention design. External writers, additional workers or replicas need their own coordination; this is not an HA backup recipe.

pg_restore --list and tar -tzf in the backup block check archive readability, not recoverability. A nonzero backup status requires investigation; partial files are not accepted recovery sets. Retain both archives from the same pause together and copy them off-host securely. Acceptance still requires the isolated restore, object counts, media files, login and canary job described above.

Best Practices

  • Version-control configuration and review changes.
  • Use least privilege and explicit allow-lists.
  • Automate repeatable checks and monitor the expected state.
  • Test upgrades and restores before production rollout.

Conclusion

A reliable implementation of Install NetBox on Ubuntu with PostgreSQL and Nginx is more than a successful first run. Keep the configuration documented, observable, recoverable, and aligned with the team's change-management process.

Frequently Asked Questions

Is a PostgreSQL backup enough?

No. Retain media, configuration, secret key, pepper and plugin versions.

Can code simply be downgraded after a migration?

No. Rollback must account for schema compatibility and the pre-change database backup.

Official Sources

Share

Meet AJInfrastructure & DevOps
Loading…