Node.js with Express is one of the most widely used combinations for building web APIs and applications. Running this stack behind a Tor hidden service gives developers a fully anonymous deployment: no domain registrar, no TLS certificate authority, no CDN logging your traffic. The .onion address is the complete identity of the service. This guide walks through deploying a production-ready Node.js Express application as a Tor hidden service, with nginx as a reverse proxy, PM2 for process management, and operational security practices for maintaining anonymity.
Need this done for your project?
We implement, you ship. Async, documented, done in days.
The standard architecture places nginx as the front-end reverse proxy listening on localhost:80, with Node.js Express running on localhost:3000 (or any non-80 port), and Tor forwarding the .onion port 80 to localhost:80. This layered approach separates concerns: nginx handles static file serving, compression, and HTTP headers; Express handles application logic; Tor handles the anonymized network transport. Nginx configuration for a Tor hidden service is similar to a standard reverse proxy setup with one critical difference: remove any headers that could leak the real server IP or reveal the backend framework. Specifically, remove X-Powered-By headers (Express sets this by default - disable it in Express with app.disable('x-powered-by')), set custom Server headers to something generic, and avoid headers that reference backend hostnames. PM2 manages the Node.js process: auto-restart on crash, log management, and cluster mode for utilizing multiple CPU cores. The combination provides a production-grade anonymous web application stack.
Express Application Configuration for Onion Hosting
Configure Express specifically for Tor hidden service operation. Trust proxy settings: when nginx is the reverse proxy, Express needs app.set('trust proxy', 1) to correctly interpret X-Forwarded-For headers. However, in a Tor context, X-Forwarded-For headers are not useful for client identification (Tor exit paths for hidden services do not reveal client IPs). Disable X-Powered-By: app.disable('x-powered-by'). Set secure cookies: since .onion services inherently encrypt traffic, you may still want Secure cookie flags but note that some browsers treat .onion as a secure context (Firefox) while others do not. Use HttpOnly and SameSite=Strict cookie attributes regardless. Session management: use server-side sessions (express-session with a PostgreSQL or Redis store bound to localhost) rather than stateless JWT tokens if session revocation is a requirement. Rate limiting: apply express-rate-limit as middleware to prevent abuse, using IP-based rate limiting (note: all Tor users share exit IP space, so be careful with overly restrictive limits that would affect legitimate users).
PM2 Configuration and Process Management
PM2 is the standard process manager for Node.js in production. Install globally with npm i -g pm2. Create an ecosystem.config.js file defining the application: name, script entry point, instances (use 'max' for cluster mode using all CPU cores, or a specific number), exec_mode: 'cluster', env variables, and log file paths. Start with pm2 start ecosystem.config.js, enable auto-start on server boot with pm2 startup and pm2 save. PM2 cluster mode runs multiple Node.js worker processes behind a built-in load balancer. This is beneficial for CPU-bound requests and maintains availability during individual worker crashes. Monitor with pm2 monit (terminal dashboard showing CPU, memory, request count per worker) and pm2 logs (tailing log files). Log rotation is essential for long-running services: install pm2-logrotate module (pm2 install pm2-logrotate) and configure max file size and rotation count. Logs may contain sensitive information - configure log levels carefully and consider disabling access logs or limiting them to error-level only for privacy-sensitive deployments.
Security Headers and Information Leakage Prevention
Security headers for a .onion Express application differ from clearnet recommendations. Use the Helmet.js middleware as a baseline but configure it for the .onion context. Content-Security-Policy: define strict CSP that prevents loading resources from clearnet domains (which would de-anonymize users). A strict CSP for a .onion service: default-src 'self'; script-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src 'self'. This prevents the browser from making any requests outside your .onion service. X-Frame-Options: DENY (prevent embedding in iframes). Referrer-Policy: no-referrer (prevent referrer headers when users navigate from your .onion to any other site). Feature-Policy/Permissions-Policy: restrict access to camera, microphone, geolocation (all could de-anonymize users). Remove Server header (nginx config: server_tokens off; and a custom add_header Server 'nginx';). The combination of these headers creates a server that does not leak its backend details and actively protects user anonymity during navigation.
Deployment Workflow and Operational Security
The deployment workflow for a .onion Express application should itself be anonymized. Avoid deploying from a machine with a known identity (e.g., developer workstation with real IP) using git push to a clearnet repository, then pulling on the server - this creates a link between the developer's IP and the hidden service server. Recommended workflow: develop locally, push to a private repository accessible via Tor (or use git bundles transferred via .onion SFTP), and deploy to the server via SSH over Tor (ssh -o ProxyCommand='nc -x 127.0.0.1:9050 %h %p' user@server.onion). For dependency management, configure npm to use Tor SOCKS5 proxy during npm install on the server. Environment variables: store sensitive configuration in .env files with appropriate permissions (chmod 600), never commit .env to version control. Deployment automation: create a deployment script that runs git pull (via Tor), npm ci --production, and pm2 reload app. Test the full request path after each deployment by making a test request via torsocks curl to your .onion address.