en

Django Tor Hidden Service: Complete Setup Tutorial 2026

Django's robust security features, authentication system, and admin interface make it an excellent choice for hidden service applications. This tutorial covers deploying a production Django app as a .onion service, from security settings to Gunicorn/Nginx configuration.

Need this done for your project?

We implement, you ship. Async, documented, done in days.

Start a Brief

Django Security Settings for Hidden Services

Django's settings.py has security settings that need adjustment for .onion deployment. ALLOWED_HOSTS: set to ['youraddress.onion', '127.0.0.1', 'localhost']. Without this, Django rejects all requests with a 400 error. CSRF_TRUSTED_ORIGINS: for Django 4.0+, add ['http://youraddress.onion', 'https://youraddress.onion'] if using CSRF protection across different ports. SESSION_COOKIE_SECURE: set True only if your .onion is served over HTTPS (some hidden services use HTTP internally). For HTTP-only .onion: set SESSION_COOKIE_SECURE = False (the Tor transport provides encryption). CSRF_COOKIE_SECURE: same as SESSION_COOKIE_SECURE. SECURE_SSL_REDIRECT: set False for HTTP .onion services (the Tor network provides transport security). X_FRAME_OPTIONS = 'DENY': keep this enabled regardless of HTTP/HTTPS. Content-Security-Policy: configure via django-csp middleware or manually in Nginx headers. For .onion services, default-src 'self' with appropriate exceptions for your application.

Gunicorn Configuration for .onion Services

Gunicorn (Green Unicorn) serves Django applications via WSGI. For hidden services: bind to Unix socket (better performance than TCP for local connections): gunicorn --bind unix:/run/gunicorn/gunicorn.sock myapp.wsgi:application. Workers: use 2*(CPU cores)+1 workers. For a 2-core VPS: 5 workers. Worker class: sync workers for CPU-bound apps, gevent or asyncio workers for I/O-bound apps (database queries, external API calls). Timeout: 120 seconds is appropriate for Tor's variable latency. Systemd service for Gunicorn: create /etc/systemd/system/gunicorn.service with Type=notify, ExecStart=/path/to/gunicorn command, ExecReload=kill -s HUP $MAINPID, RuntimeDirectory=gunicorn, RuntimeDirectoryMode=755. Enable with systemctl enable gunicorn, start with systemctl start gunicorn.

Nginx Configuration for Django .onion Service

Nginx proxies HTTP requests from Tor to Gunicorn. Configuration for Django hidden service: server block listening on 127.0.0.1:PORT (not 0.0.0.0), proxy_pass to Unix socket (proxy_pass http://unix:/run/gunicorn/gunicorn.sock;), static file serving (Django's staticfiles at a configured STATIC_ROOT path), and media file serving if applicable. Critical headers: proxy_set_header Host $host; (passes the .onion hostname to Django, required for ALLOWED_HOSTS validation), proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; (though useless for .onion, include for consistency), and proxy_set_header X-Forwarded-Proto $scheme; (informs Django of the protocol). Django STATIC_ROOT: run python manage.py collectstatic before deployment, serve static files directly from Nginx (faster than Django's development server).

Django Admin and Staff Access Over .onion

Django's admin interface is accessible at /admin/ by default. For hidden services: keep admin at /admin/ (obscurity is not security), protect with two-factor authentication (django-otp or django-two-factor-auth), restrict admin access to specific sessions (middleware that checks for a cookie set during a privileged authentication step), and consider moving admin to a separate .onion address (different private key, different introduction points - admin operations cannot be correlated with user-facing traffic). Staff authentication: all admin logins should use strong passwords and 2FA. Session expiry for admin: SESSION_COOKIE_AGE = 3600 (1 hour) for admin sessions is appropriate. Admin actions are logged by Django's built-in admin log - retain these for security auditing.

Database Configuration and Migration Management

PostgreSQL is the recommended database for production Django hidden services. Connection: use Unix socket for local PostgreSQL (faster than TCP loopback). In settings.py: DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydb', 'USER': 'myuser', 'HOST': '/var/run/postgresql/', 'PORT': ''}}. Database security: PostgreSQL listens on localhost only (not 0.0.0.0). Database user has only the permissions needed (no superuser). Enable pg_hba.conf local authentication. Migrations in production: test all migrations in staging before production, never run migrations on production without a backup, use django-safemigrate for destructive migration checks. Backup schedule: daily pg_dump to encrypted off-server storage. Connection pooling: django-db-connection-pool or pgBouncer for high-traffic sites that exhaust PostgreSQL's connection limit.

Why Anubiz Host

100% async — no calls, no meetings
Delivered in days, not weeks
Full documentation included
Production-grade from day one
Security-first approach
Post-delivery support included

Ready to get started?

Skip the research. Tell us what you need, and we'll scope it, implement it, and hand it back — fully documented and production-ready.

Anubiz Chat AI

Online