Deploy Django on FreeBSD With Gunicorn, Nginx, and rc.d

Deploy Django on FreeBSD 14: install Python, PostgreSQL, and Nginx from packages, run Gunicorn as an rc.d service with daemon(8), configure pf firewall rules, get certificates with acme.sh or certbot, and optionally isolate the app in a jail.

Problem statement

Most Django deployment guides assume Linux with systemd. FreeBSD has no systemd: services are rc.d scripts, packages come from pkg, the firewall is pf, and isolation is done with jails. Django, Gunicorn, PostgreSQL, and Nginx all run well on FreeBSD, but the glue (service supervision, users, paths under /usr/local) is different enough that Linux instructions do not translate directly.

Django on FreeBSDpkg installpython, nginx, pgVirtualenvapp and Gunicornrc.d servicedaemon(8) restartspf + TLSfirewall, acmeFreeBSD replaces systemd with rc.d and daemon(8), and keeps configs under /usr/local/etc.
  1. pkg install python312 py312-sqlite3 postgresql17-server nginx git.
  2. Create a www-style app user, a virtualenv, and install requirements plus Gunicorn.
  3. Write an rc.d script for Gunicorn using daemon(8) with -r for automatic restarts, and enable it in /etc/rc.conf.
  4. Configure Nginx in /usr/local/etc/nginx/nginx.conf to proxy to Gunicorn's socket.
  5. Enable pf with rules for SSH, HTTP, and HTTPS, and issue certificates with security/py-certbot or acme.sh.

Step-by-step solution

1. Install packages

pkg install -y python312 py312-sqlite3 postgresql17-server postgresql17-client nginx git
sysrc postgresql_enable=YES nginx_enable=YES
service postgresql initdb
service postgresql start

Python's standard library modules such as sqlite3 are separate packages on FreeBSD; install the ones your project needs. PostgreSQL's data directory is under /var/db/postgres.

2. Create the app environment

pw useradd shop -m -d /usr/local/www/shop -s /usr/sbin/nologin
su -m shop -c "git clone https://github.com/acme/shop.git /usr/local/www/shop/src"
su -m shop -c "python3.12 -m venv /usr/local/www/shop/venv"
su -m shop -c "/usr/local/www/shop/venv/bin/pip install -r /usr/local/www/shop/src/requirements.txt gunicorn"

Packages with C extensions, such as psycopg, may build from source on FreeBSD because PyPI wheels target Linux and macOS. Install the PostgreSQL client package first so pg_config exists, or use psycopg[c].

what changes on FreeBSDUbuntuFreeBSDService managersystemd unitsrc.d + daemon(8)PackagesaptpkgConfig location/etc/usr/local/etcFirewallufw / nftablespfIsolationcontainersjailsThe app layer is the same; the operating system glue differsdjango-deployment.com
Django settings, Gunicorn flags, and Nginx proxy rules stay unchanged.

3. Write an rc.d script for Gunicorn

#!/bin/sh
# /usr/local/etc/rc.d/shop
# PROVIDE: shop
# REQUIRE: LOGIN postgresql
# KEYWORD: shutdown

. /etc/rc.subr

name="shop"
rcvar="shop_enable"
load_rc_config $name
: ${shop_enable:="NO"}
: ${shop_user:="shop"}
: ${shop_dir:="/usr/local/www/shop/src"}
: ${shop_env_file:="/usr/local/etc/shop.env"}

pidfile="/var/run/${name}/daemon.pid"
command="/usr/sbin/daemon"
command_args="-r -P ${pidfile} -u ${shop_user} -o /var/log/${name}.log \
  /usr/local/www/shop/venv/bin/gunicorn --chdir ${shop_dir} \
  --bind unix:/var/run/${name}/gunicorn.sock --workers 3 config.wsgi"

start_precmd="${name}_prestart"
shop_prestart() {
    install -d -o ${shop_user} -g www -m 750 /var/run/${name}
}

run_rc_command "$1"

daemon -r restarts Gunicorn if it dies, which covers what Restart=always does under systemd. Load environment variables by setting shop_env_file and sourcing it in the prestart function, or let Django read them with django-environ. Then:

chmod +x /usr/local/etc/rc.d/shop
sysrc shop_enable=YES
service shop start

4. Configure Nginx

Edit /usr/local/etc/nginx/nginx.conf:

upstream shop { server unix:/var/run/shop/gunicorn.sock fail_timeout=0; }
server {
    listen 80;
    server_name shop.example.com;
    location /static/ { alias /usr/local/www/shop/static/; }
    location / {
        proxy_pass http://shop;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Nginx runs as www, which matches the socket directory group in the rc.d script.

5. Firewall and certificates

cat > /etc/pf.conf <<'PF'
ext_if = "vtnet0"
set skip on lo0
block in all
pass out all keep state
pass in on $ext_if proto tcp to port { 22 80 443 } keep state
PF
sysrc pf_enable=YES && service pf start
pkg install -y py311-certbot py311-certbot-nginx
certbot --nginx -d shop.example.com

Check the certbot package names with pkg search certbot, since they track the ports tree's default Python. Add renewal to /etc/periodic.conf or cron.

host or jail?How many apps share the server?one app, small serverRun on hostrc.d + pfseveral apps or clientsOne jail per appBastille, iocageneed Linux imagesUse a Linux VMor Linux hostJails give container-like isolation without Dockerdjango-deployment.com
Docker does not run natively on FreeBSD; plan around jails instead.

Explanation

FreeBSD's base system and third-party software are separated: anything installed with pkg lives under /usr/local, including its configuration in /usr/local/etc and rc.d scripts in /usr/local/etc/rc.d. Services are enabled with variables in /etc/rc.conf. There is no built-in process supervisor like systemd, so daemon(8) wraps Gunicorn to restart it and manage its PID. Everything above the operating system, including Django's settings and Gunicorn's options, is identical to a Linux deployment.

Edge cases / notes

  • Wheels. Expect some packages to compile from source; install compilers and headers (pkg install rust for cryptography if no binary package exists, or use py312-cryptography from pkg).
  • Log rotation. Add the log file to /etc/newsyslog.conf.d/ so it rotates.
  • ZFS. Snapshots of the app and database datasets before deploys make rollback straightforward.

Next steps

FAQ

Does Django run on FreeBSD?

Yes. Django is pure Python and runs on any platform with a supported Python version.

What replaces systemd on FreeBSD?

rc.d scripts for starting and stopping services, and daemon(8) for supervising and restarting long-running processes like Gunicorn.

Can I use Docker on FreeBSD?

Not natively. Use jails for isolation, or run Linux in a bhyve VM if you need container images.

Are Python packages installed with pip or pkg?

Use pip in a virtualenv for the app's dependencies. Use pkg for Python itself and system services.

Keep going