Complete guide to installing self-hosted n8n on a FreeBSD server. I describe step-by-step the process of configuring a highly efficient and secure system environment. The technologies used include: FreeBSD, PF firewall, Nginx as a reverse proxy, SSL, and Redis with Queue Mode enabled. I use this solution in production in my own projects.

One of my sample n8n workflows running on a FreeBSD server set up according to this guide: an automated competitive analysis task utilizing OpenAI to process data retrieved via the Reddit API.
n8n is a powerful workflow automation tool (like Zapier or Make), allowing you to create simple automations between applications as well as complex processes with conditional logic, AI, webhooks, and data transformation. Besides paid subscription plans, n8n also offers the option to install on your own server.
Detailed guide assumptions and technologies used:
- n8n running as a FreeBSD system service – automatic startup, restarts and system integration
- PF firewall with attack protection – multi-layer protection against port scanning, SSH brute-force attacks and DDoS
- Latest Node.js LTS version – stable runtime environment with long-term support
- Nginx as reverse proxy with SSL/TLS – secure, encrypted communication following industry best practices
- Automatic Let’s Encrypt certificate renewal
- Redis and Queue Mode for increased performance – faster workflow execution and reliable queuing

n8n on FreeBSD – conceptual diagram of the presented solution including installation and configuration of PF firewall, Node.js, Redis, Nginx with SSL certificate.
FreeBSD for n8n
FreeBSD has long been my first choice system for production servers due to its reliability, security, and predictability. FreeBSD has a well-thought-out architecture where the kernel and userland are developed as a cohesive whole. The system uses fewer resources than a typical Linux with systemd and its entire ecosystem. Hence my choice of FreeBSD as the base for n8n.
n8n Self Hosted
The free version of n8n, i.e. self-hosted with the Sustainable Use License, covers personal use, small companies below a certain employee or revenue limit, non-profit organizations, and educational projects. This solution provides full control over installation and data, which is especially important from security and privacy perspectives. The only real limitation is the inability to run your own competing n8n hosting service for other companies, but for normal production use, it is a fully functional and free solution.
Assumptions and System Requirements for n8n
n8n is surprisingly lightweight and even a typical VPS is more than sufficient to run it. Minimum requirements are really low – n8n can run on as little as 512MB RAM and a single CPU core. Of course, this is the absolute minimum where the system will work but very slowly, especially with more complex workflows. My VPS parameters are: 2 × vCPU AMD EPYC 3GHz+, 8 GB RAM, 250GB NVMe and 1Gb/s link.
n8n is a Node.js application that, at rest (without executing workflows), occupies about 150-300MB RAM. Real load appears only during work. If automations process large files, perform complex data transformations, or communicate with many APIs simultaneously, then resource requirements naturally increase.
The configuration with Redis, which I recommend, significantly improves performance. Redis acts as a buffer and queue manager, allowing n8n to manage multiple parallel workflows more efficiently. Redis itself is also very lightweight – it typically occupies about 30-50MB RAM in basic configuration. Thanks to Queue Mode, n8n will better manage resources, distributing work to separate workers and avoiding server overload.
PF Firewall Configuration
The firewall, in this case PF, is not optional for a production n8n installation – it is the foundation of security. n8n often stores sensitive data – credentials for various API services, access tokens, workflow information that may contain business logic. PF ensures that the only open ports are those that really must be accessible from outside – in our configuration: 22 for SSH and 80/443 for HTTP/HTTPS traffic through Nginx. All other ports are blocked by default.
PF is extremely fast, efficient, and has very readable configuration syntax. Unlike some other firewall solutions, PF operates at the FreeBSD kernel level, giving it excellent performance – even with intense network traffic, the overhead is minimal.
Creating PF Configuration File
Create or edit the file /etc/pf.conf with the following configuration::
# File: /etc/pf.conf
# Makra
ext_if="vtnet0" # Change to the name of the appropriate network interface
tcp_services = "{ 22, 80, 443 }"
icmp_types = "{ echoreq, unreach }"
# Options
set block-policy drop
set skip on lo0
# Scrubbing
scrub in all
# Default rules
block all
pass out quick on $ext_if keep state
# Anti-spoofing
antispoof quick for $ext_if
# Allow SSH, HTTP and HTTPS
pass in quick on $ext_if proto tcp to ($ext_if) port $tcp_services keep state
# Allow ICMP (ping)
pass in quick on $ext_if inet proto icmp icmp-type $icmp_types keep state
# Rate limiting for SSH (brute-force protection)
pass in quick on $ext_if proto tcp to ($ext_if) port 22 \
keep state (max-src-conn 15, max-src-conn-rate 5/3, \
overload <bruteforce> flush global)
# Table for IPs conducting attacks
table <bruteforce> persist
block quick from <bruteforce>
# Reject connection attempts from the internet to private/special addresses
table <rfc6890> { 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 \
172.16.0.0/12 192.0.0.0/24 192.0.0.0/29 192.0.2.0/24 192.88.99.0/24 \
192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 \
240.0.0.0/4 255.255.255.255/32 }
block in quick on egress from <rfc6890>
block return out quick on egress to <rfc6890>
Enable and start PF at system boot:
sysrc pf_enable="YES"
sysrc pflog_enable="YES"
Check configuration syntax:
pfctl -nf /etc/pf.conf
Start PF firewall:
service pf start
service pflog start
Node.js Installation
n8n is built on Node.js – a JavaScript runtime environment for the server side. Node.js is known for high performance thanks to its asynchronous I/O model and Google’s V8 engine.
n8n requires Node.js version 18.x or newer. We will install the latest stable LTS version:
pkg install -y node24 npm-node24
Verify installation:
node --version
npm --version
n8n Installation
n8n will be installed globally in the system:
npm install -g n8n
Verify installation:
n8n --version
For security reasons, we will create a dedicated system user who has only the permissions absolutely necessary for the application to function. This also facilitates security auditing – in the logs, it will be clearly visible what exactly the n8n process is doing, among the actions of other services.
pw useradd n8n -m -s /usr/sbin/nologin -c "n8n Workflow Automation"
Next, we create the n8n data directory:
mkdir -p /home/n8n/.n8n
chown -R n8n:n8n /home/n8n/.n8n
n8n Environment Variables Configuration
n8n is configured through environment variables in the file /home/n8n/.n8n/.env. n8n automatically loads this file at startup. This is standard in the Node.js ecosystem – simple key=value format without any additional commands.
# File: /home/n8n/.n8n/.env
# Basic configuration
N8N_HOST=0.0.0.0
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://YOUR_DOMAIN_NAME/
N8N_EDITOR_BASE_URL=https://YOUR_DOMAIN_NAME/
# Localization
GENERIC_TIMEZONE=Europe/Warsaw
# Logging
N8N_LOG_LEVEL=info
N8N_LOG_OUTPUT=console,file
N8N_LOG_FILE_LOCATION=/home/n8n/.n8n/logs/
# Execution management
EXECUTIONS_DATA_SAVE_ON_ERROR=all
EXECUTIONS_DATA_SAVE_ON_SUCCESS=all
EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=true
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
Explanation of key variables:
N8N_HOST=0.0.0.0– n8n will listen on all network interfaces. Since we are behind PF firewall and Nginx reverse proxy, this is safe. Alternatively, you can use 127.0.0.1 (localhost only), but this requires additional Nginx configuration.N8N_PORT=5678– internal port on which n8n runs. This port is not accessible from outside thanks to the firewall, access is only through Nginx through ports 80 and 443.N8N_PROTOCOL=https– informs n8n that access is via HTTPS. This is important for correct webhook and link generation.WEBHOOK_URL– base URL for webhooks. Many n8n integrations use webhooks to receive data from external services in real-time.GENERIC_TIMEZONE– time zone for logs and scheduler. Setting the correct zone ensures that scheduled workflows start at the appropriate local times.N8N_LOG_LEVEL– logging level. Options: error, warn, info, verbose, debug. “info” is a good balance between detail and log readability.EXECUTIONS_DATA_SAVE_ON_ERROR/SUCCESS=all– saves complete execution data, which facilitates debugging. Alternatives are none (don’t save) or save (save only to database, not to files). For production environments, all gives the best insight into workflow operation.EXECUTIONS_DATA_PRUNE=true+EXECUTIONS_DATA_MAX_AGE=168– automatically removes executions older than 168 hours (7 days). Prevents database growth.
We must also manually add the log directory and n8n log files:
mkdir -p /home/n8n/.n8n/logs
chown -R n8n:n8n /home/n8n/.n8n
System Service Configuration for n8n
The next step is to create an rc.d system service (daemon) for FreeBSD’s init system: /usr/local/etc/rc.d/n8n. Unlike systemd used in Linux, FreeBSD’s rc.d is characterized by simplicity and clarity. This is one of the reasons I choose FreeBSD.
Why create a system service at all?
- Automatic n8n startup at server boot
- Easy management through standard service commands
- Automatic restarts in case of failure (thanks to the -r option in daemon)
- Consistent logging to system files
- Integration with FreeBSD’s process management system
We create and edit the file /usr/local/etc/rc.d/n8n, which will launch the default configuration from the file /home/n8n/.n8n/.env. Please note that we are creating a service tailored to the n8n configuration run at the user level of the n8n user we previously created in the system.
#!/bin/sh
# PROVIDE: n8n
# REQUIRE: LOGIN DAEMON NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="n8n"
rcvar="n8n_enable"
load_rc_config $name
: ${n8n_enable:="NO"}
pidfile="/var/run/n8n.pid"
start_cmd="n8n_start"
stop_cmd="n8n_stop"
status_cmd="n8n_status"
n8n_start()
{
echo "Starting n8n"
touch /var/run/n8n.pid
/usr/sbin/daemon -p /var/run/n8n.pid -o /var/log/n8n.log \
su -m n8n -c "cd /home/n8n && \
HOME=/home/n8n \
PATH=/usr/local/bin:/usr/bin:/bin \
/bin/sh -c 'set -a; . /home/n8n/.n8n/.env; set +a; exec /usr/local/bin/n8n start'"
}
n8n_stop()
{
echo "Stopping n8n"
# Zabij wszystkie procesy n8n użytkownika n8n
pkill -u n8n -f "node.*n8n"
# Poczekaj chwilę
sleep 2
# Wyczyść PID file
rm -f /var/run/n8n.pid
# Sprawdź czy zatrzymane
if pgrep -u n8n -f "node.*n8n" > /dev/null; then
echo "Force killing n8n"
pkill -9 -u n8n -f "node.*n8n"
sleep 1
fi
echo "n8n stopped"
}
n8n_status()
{
# Sprawdź czy proces działa (szukaj node z n8n)
if pgrep -u n8n -f "node.*n8n" > /dev/null; then
pid=$(pgrep -u n8n -f "node.*n8n")
echo "${name} is running as pid ${pid}"
sockstat -l | grep ${pid} | grep 5678
return 0
else
echo "${name} is not running"
return 1
fi
}
run_rc_command "$1"
Grant execution permissions to the n8n system service:
chmod +x /usr/local/etc/rc.d/n8n
Create the system service log file and write permissions for the n8n user:
touch /var/log/n8n.log
chown n8n:n8n /var/log/n8n.log
Enable the n8n service at system startup:
sysrc n8n_enable="YES"
Redis Installation and Configuration
Redis (Remote Dictionary Server) is an advanced key-value database storing data in RAM. It is one of the most popular NoSQL databases used for caching, user sessions, and task queues.
How does Redis speed up n8n?
- Task queuing – n8n uses the Bull library (based on Redis) to manage the workflow execution queue
- Cache – frequently used data is stored in Redis instead of reading it repeatedly from disk
- Sessions – user session information is stored in fast memory
- State sharing – in multi-instance (cluster) installations, Redis synchronizes state between instances
Redis stores all data in RAM, which gives microsecond access times – hundreds of times faster than traditional databases. However, this also means that Redis requires an appropriate amount of memory.
Install Redis:
pkg install -y redis
Enable Redis at system startup and start it:
sysrc redis_enable="YES"
service redis start
Verify Redis operation in the system:
redis-cli ping
The command should return: PONG
Basic Redis Configuration
By default, Redis will be used as an in-memory database. In a later step (Optimizing n8n with Redis and Queue Mode), we will configure it as a queue and cache for n8n.
First, we edit the file /usr/local/etc/redis.conf:
# File: /usr/local/etc/redis.conf:
maxmemory 2gb
maxmemory-policy allkeys-lru
timeout 300
tcp-keepalive 60
and restart Redis:
service redis restart
Explanation of Redis configuration parameters:
maxmemory 2gb– limits Redis memory usage to 2 GB. This is protection against using all available RAM, which could cause system problems. For small and medium n8n installations, 2 GB RAM will be quite sufficient, should handle over 100 workflows.maxmemory-policy allkeys-lru– specifies the data eviction strategy when Redis reaches the memory limit. LRU (Least Recently Used) removes the least frequently used keys. This is a good default policy for cache. Alternative options include:volatile-lru– removes least recently used among keys with TTLnoeviction– returns errors when memory is full (not recommended for n8n)
timeout 300– disconnects inactive client connections after 300 seconds (5 minutes). Prevents accumulation of “dead” connections that would consume resources.tcp-keepalive 60– sends TCP keepalive packets every 60 seconds. Helps detect broken network connections and keeps connections active through firewalls that might close them due to inactivity.
By default, Redis only listens on localhost (127.0.0.1), which is safe and sufficient for the presented configuration.
Configuring n8n to Work with Redis
We already have Redis configured in the system. Next, we configure n8n to work with Redis. To do this, we edit the file /home/n8n/.n8n/.env again and add new lines at the end:
# Enable Redis cache
N8N_CACHE_ENABLED=true
CACHE_REDIS_HOST=localhost
CACHE_REDIS_PORT=6379
CACHE_REDIS_DB=1

Another example n8n workflow running on FreeBSD server built based on this guide. The workflow is a modified version of the competitor analysis solution presented above. Here it uses Google Gemini AI to process data downloaded via Reddit API.
Nginx Installation
Nginx is one of the most popular web servers in the world, serving over 30% of websites. It was designed with high performance and low resource usage in mind. In our configuration, we use Nginx as a reverse proxy, i.e., an intermediary server between clients (browsers) operating on port 80/443 and the n8n application server on port 5678. Simply put: clients connect to the reverse proxy, which then forwards requests to n8n.
Key advantages of Nginx reverse proxy:
- SSL/TLS handling – Nginx takes care of encryption, offloading n8n from this task
- Security – n8n is not directly accessible from the internet
- Load balancing – ability to distribute traffic between multiple n8n instances and/or e.g., an Apache-based web server
- Caching – acceleration through static resource caching
- Compression – reducing data transfer through gzip
- Rate limiting – protection against excessive traffic
- Easier certificate management – single point of SSL management
- Multi-hosting – ability to host multiple applications on one server
In professional installations, Nginx often handles thousands of connections simultaneously with minimal resource usage – typically below 10 MB RAM.
Install Nginx:
pkg install -y nginx
We will perform detailed Nginx configuration for n8n in the Configuring Nginx as reverse proxy for n8n section, but now we will add additional security parameters to the main Nginx settings file /usr/local/etc/nginx/nginx.conf – Rate Limiting Zones for n8n:
# File: /usr/local/etc/nginx/nginx.conf
http {
include mime.types;
default_type application/octet-stream;
# ADD THE FOLLOWING RATE LIMITING ZONES DEFINITIONS
limit_req_zone $binary_remote_addr zone=login_global:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=api_global:10m rate=50r/s;
#... (rest of configuration)
Variable explanation:
zone=login_global:10m– 10MB memory for IP tracking (approx. 160k IP addresses)zone=api_global:10m– 10MB memory for IP tracking (approx. 160k IP addresses)rate=5r/s– max 5 requests per second (300/min) for login endpointsrate=50r/s– max 50 requests per second (3000/min) for general API
We will reference Rate Limiting Zones in a separate n8n configuration file (described in Configuring Nginx as reverse proxy for n8n).
At the end of this chapter, let’s also add enabling Nginx at system startup:
sysrc nginx_enable="YES"
Nginx Configuration – Working with SSL Certificate
The default Nginx installation on FreeBSD only creates the directory /usr/local/etc/nginx/. We will create a file structure for additional configuration files:
mkdir -p /usr/local/etc/nginx/conf.d
We make changes to the main Nginx configuration so that additional configuration files are loaded at startup. We edit the file /usr/local/etc/nginx/nginx.conf. In the http section, we add an additional line:
# File: /usr/local/etc/nginx/nginx.conf
http {
include mime.types;
default_type application/octet-stream;
# THE FOLLOWING LINE SHOULD BE ADDED:
include /usr/local/etc/nginx/conf.d/*.conf;
# ... (rest of configuration)
}
Now we need to create a temporary configuration for the Nginx server to obtain an SSL certificate for our domain. We create and edit the file /usr/local/etc/nginx/conf.d/temp.conf:
# File: /usr/local/etc/nginx/conf.d/temp.conf
server {
listen 80;
server_name YOUR_DOMAIN_NAME;
root /usr/local/www;
location /.well-known/acme-challenge/ {
try_files $uri =404;
}
}
We verify the Nginx configuration and start the server:
nginx -t
service nginx start
SSL Certificate Configuration
Only from this point, when Nginx with temporary configuration is running as a web server, can we generate an SSL certificate for our domain.
Nginx natively supports SSL/TLS encryption. Since version 1.25+, there is the ngx_http_acme_module module, which allows Nginx to communicate directly with Let’s Encrypt without external tools. However, it is not available in the standard FreeBSD ports tree package and requires compiling Nginx from source with the –with-http_acme_module flag. Therefore, to simplify, our configuration will be based on Let’s Encrypt with Certbot, which automates the process of obtaining and renewing certificates.
Why is SSL/TLS crucial?
- Data encryption – protects passwords, API credentials, and sensitive data from eavesdropping.
- Integrity – ensures that data has not been modified in transit.
- Many API requirements – many modern APIs require HTTPS for webhooks.
Installing and using Certbot on FreeBSD to verify our server through Let’s Encrypt and obtain a free SSL certificate:
pkg install -y py311-certbot py311-certbot-nginx
certbot certonly --webroot \
-w /usr/local/www \
-d YOUR_DOMAIN_NAME \
--email adress@YOUR_DOMAIN_NAME \
--agree-tos \
--no-eff-email
After configuring and obtaining certificates, we can do a test:
openssl s_client -connect YOUR_DOMAIN_NAME:443 -servername YOUR_DOMAN_NAME
and check the certificate expiration date:
echo | openssl s_client -connect YOUR_DOMAIN_NAME:443 2>/dev/null | openssl x509 -noout -dates
Automatic SSL Certificate Renewal
Let’s Encrypt certificates are valid for 90 days. This is a shorter period than traditional CAs (usually a year or more), but it is intentional – it forces renewal automation and reduces the time window in case of key compromise. We add a cron job for automatic renewal:
crontab -e
and add the following line:
0 3 * * * /usr/local/bin/certbot renew --quiet --post-hook "service nginx reload"
We run the task daily at 3:00 AM (time chosen deliberately when traffic is lowest). If our server will handle more certificates, the renew parameter checks them all and renews only those expiring within 30 days. Certbot decides itself whether renewal is needed.
We can also test the renewal process without actually renewing the certificate with the command:
certbot renew --dry-run
This simulates the entire process and informs about potential problems. I recommend running this test right after configuration.
The final step is to verify the Nginx configuration and start it with SSL certificate support:
nginx -t
service nginx reload
Now we can remove the temporary Nginx configuration:
rm /usr/local/etc/nginx/conf.d/temp.conf
Configuring Nginx as Reverse Proxy for n8n
When Nginx with the appropriate domain already handles the SSL certificate, we can create a dedicated configuration for n8n in Nginx by creating and editing the file /usr/local/etc/nginx/conf.d/n8n.conf:
# File: /usr/local/etc/nginx/conf.d/n8n.conf
upstream n8n_backend {
server 127.0.0.1:5678;
keepalive 64;
}
server {
listen 80;
server_name YOUR_DOMAIN_NAME;
# HTTP to HTTPS redirect
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name YOUR_DOMAIN_NAME;
# SSL certificate paths (we will configure in the next step)
ssl_certificate /usr/local/etc/letsencrypt/live/YOUR_DOMAIN_NAME/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/YOUR_DOMAIN_NAME/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logs
access_log /var/log/nginx/n8n_access.log;
error_log /var/log/nginx/n8n_error.log;
# Size limits
client_max_body_size 50M;
# Using rate limiting zones from nginx.conf
location /rest/login {
limit_req zone=login_global burst=3 nodelay;
limit_req_status 429;
proxy_pass http://n8n_backend;
proxy_http_version 1.1;
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;
}
location /rest/ {
limit_req zone=api_global burst=20 nodelay;
limit_req_status 429;
proxy_pass http://n8n_backend;
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
location /webhook/ {
limit_req zone=api_global burst=50 nodelay;
limit_req_status 429;
proxy_pass http://n8n_backend;
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
location / {
proxy_pass http://n8n_backend;
proxy_http_version 1.1;
# Proxy headers
proxy_set_header Connection '';
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
# Buffering
proxy_buffering off;
proxy_cache_bypass $http_upgrade;
}
}
Notable configuration items:
upstream n8n_backend– defines the backend server (n8n). The upstream block allows defining multiple servers for load balancing. keepalive 64 maintains up to 64 open connections to the backend, reducing the overhead of creating new connections.ssl_ciphers– list of encryption algorithms. We prefer modern ECDHE ciphers with GCM (Galois/Counter Mode), which offer perfect forward secrecy – even if the server’s private key leaks in the future, previous communication remains secure.client_max_body_size 50M– maximum size of uploaded files. n8n can process large files in workflows, so 50 MB is a reasonable value. Adjust according to needs.WebSocket support– n8n uses WebSocket for real-time communication between the browser and server. Upgrade and Connection headers are crucial for proper n8n interface operation.proxy_buffering off– disables response buffering. For real-time applications like n8n, we want immediate data transfer without waiting for a buffer.location /rest/login,location /rest/,location /webhook/– these configurations implement layered rate limiting protection for different n8n endpoints, where each location has a limit tailored to its risk and expected usage pattern:- The
/rest/loginendpoint has the strictest protection – only 10 requests per minute with burst=3, because it’s the most vulnerable attack point. Brute-force password attempts always start here, so drastically limiting the number of login attempts from a single IP effectively prevents automated dictionary attacks. Burst=3 means we allow three quick attempts (e.g., password mistake), but then the limit is absolute. - General API
/rest/gets a moderate limit of 100 requests per minute with burst=20. These are endpoints for managing workflows, credentials, executions – normal use generates moderate traffic, but there shouldn’t be hundreds of requests per minute from a single IP. Burst=20 gives flexibility for legitimate operations that may generate a series of requests, but prevents abuse. - Webhooks
/webhook/have the same rate (100/min) but a much higher burst=50, because external systems often send series of webhooks at once – for example, during data synchronization or after an event that triggers multiple workflows simultaneously. High burst allows for these legitimate traffic spikes, but the base rate still protects against server flooding. - All locations use nodelay which means excess requests are immediately rejected with code 429 (Too Many Requests), instead of being queued. The
proxy_set_headerheaders ensure that n8n sees the client’s real IP address (not Nginx’s IP) and knows the connection came through HTTPS, which is important for logs and n8n security features.
- The
This approach gives us precise control – we protect what’s most important (login) the most, but we don’t block legitimate use of API and webhooks.
Next, we check the Nginx configuration and reload:
nginx -t
service nginx reload
Starting n8n
At this configuration stage, we already have n8n installed and configured with Redis support, Nginx as a reverse proxy, and an SSL certificate for the domain under which the application operates.
So let’s start n8n:
service n8n start
Check n8n status and possibly also logs:
service n8n status
tail -f /var/log/n8n.log
To verify the correct installation of n8n, open a browser and go to https://YOUR_DOMAIN_NAME. We should have access to the screen for creating a new account in our n8n instance.
We can also of course check the processes:
ps aux | grep n8n | grep -v grep
Example output:
n8n 67193 43.8 12.2 8351532 507744 - S 22:03 0:00.05 node /usr/local/bin/n8n start root 67192 0.0 0.1 14380 2708 - S 22:03 0:00.00 su -m n8n -c cd /home/n8n && HOME=/home/n8n PATH=/usr/local/bin:/usr/bin:/bin /usr/local/bin/n8n start
A small note here. We see two processes and this is a standard approach!
Process root 67192 is a wrapper process that changes the user from root to n8n. It must remain running because it is the parent of the n8n process and manages the environment and passes signals.
Process n8n 67193 is the actual n8n process. It runs as the n8n user (security!) and is a child process of the su process.
We can also verify which process is actually listening by issuing the command:
sockstat -l | grep 5678
n8n node 67193 15 tcp46 *:5678 *:*
If we want to have only one process, we can achieve this using sudo instead of su, but this requires additional configuration, and the current solution is simpler and works well.
Optimizing n8n with Redis and Queue Mode
After basic installation, we can significantly increase n8n performance by configuring Redis to work with n8n in Queue Mode. This is an optional but recommended optimization for production environments.
Queue Mode is an operating mode for n8n where workflows are not executed directly in the main process but are sent to a task queue managed by Redis (Bull Queue library). Otherwise, tasks are simply queued until resources are available.
Benefits of Queue Mode in n8n:
1. Better scalability
- Ability to run multiple workers
- Parallel workflow execution on different servers
- Horizontal scaling (adding new worker instances)
2. Greater reliability
- Execution isolation – failure of one workflow doesn’t kill the main process
- Automatic worker restart on errors
- Queue persistence – workflows don’t disappear on restart
3. Better performance
- Main process handles only UI and API
- Workers focus only on executing workflows
- Task prioritization capability
4. Advanced features
- Scheduled jobs with precise timing
- Execution rate limiting
- Real-time queue monitoring
Queue Mode Configuration
We edit the main n8n configuration file, i.e. /home/n8n/.n8n/.env and add the following variables at the end of the file:
# REDIS QUEUE MODE
# Enable queue mode - CRUCIAL!
EXECUTIONS_MODE=queue
# Redis Queue configuration
QUEUE_BULL_REDIS_HOST=localhost
QUEUE_BULL_REDIS_PORT=6379
QUEUE_BULL_REDIS_DB=0
QUEUE_HEALTH_CHECK_ACTIVE=true
QUEUE_RECOVERY_INTERVAL=60
# OPTIONAL QUEUE SETTINGS
# Number of worker processes (default 1)
N8N_WORKERS=2
# Timeout for queued tasks (seconds)
# QUEUE_WORKER_TIMEOUT=60
Explanation of key variables:
EXECUTIONS_MODE=queue– THE MOST IMPORTANT SETTING! Switches n8n from regular mode to queue mode. Without this, all other queue settings are ignored.QUEUE_BULL_REDIS_DB=0– Redis database number for the queue. Redis allows for 16 separate databases (0-15) in one instance. We use separate databases for queue and cache for better isolation. (CACHE_REDIS_DB=1we set earlier, this is a separate Redis database for cache, different from the queue = DB 0, ensures that cache operations don’t affect queue performance).QUEUE_HEALTH_CHECK_ACTIVE=true– enables periodic queue health checking. n8n will monitor whether Redis is available and whether the queue is working correctly.QUEUE_RECOVERY_INTERVAL=60– every 60 seconds n8n checks if there are “stuck” tasks in the queue (e.g., after worker failure) and tries to recover them.N8N_WORKERS– number of worker processes to run. Leave commented for standard installation (1 worker is enough). For larger installations, you can increase to 2-4.
After adding Queue Mode configuration in n8n, we restart the n8n service:
service n8n restart
Queue Mode Verification
Below are several methods to check if n8n actually uses Redis in queue mode.
- Bull library availability:
ls -d /usr/local/lib/node_modules/n8n/node_modules/*bull* 2>/dev/null
drwxr-xr-x 4 root wheel 512B Nov 12 17:10 /usr/local/lib/node_modules/n8n/node_modules/bull
npm list --prefix /usr/local/lib/node_modules/n8n bull 2>&1 | head -20
n8n@1.119.1 /usr/local/lib/node_modules/n8n ├─┬ @rudderstack/rudder-sdk-node@2.1.4 │ └── bull@4.16.4 deduped └── bull@4.16.4
- List of parameters loaded into n8n:
PID=$(ps aux | grep "node.*n8n" | grep -v grep | awk '{print $2}')
echo "n8n PID: $PID"
n8n PID: 67588
ps eww $PID | tr ' ' '\n' | grep -i "redis\|queue\|bull\|executions_mode"
CACHE_REDIS_HOST=localhost QUEUE_BULL_REDIS_PORT=6379 CACHE_REDIS_DB=1 QUEUE_BULL_REDIS_DB=0 QUEUE_BULL_REDIS_HOST=localhost CACHE_REDIS_PORT=6379 QUEUE_RECOVERY_INTERVAL=60 QUEUE_HEALTH_CHECK_ACTIVE=true EXECUTIONS_MODE=queue
- Active Redis connections:
redis-cli client list
- Bull queue keys:
redis-cli keys "bull:*"
- List of tasks in queue:
redis-cli llen "bull:n8n:wait"
redis-cli llen "bull:n8n:active"
redis-cli llen "bull:n8n:completed"
- Real-time Redis monitoring:
redis-cli monitor
Performing any operation in n8n, we should see activity in Redis, as below:
OK 1762987961.730924 [0 127.0.0.1:49304] "get" "n8n:cache:variables" 1762987961.730951 [0 127.0.0.1:49304] "hget" "n8n:cache:workflow-project" "kJ0kaWZlHa3KwcWw" 1762987961.747233 [0 127.0.0.1:49304] "hget" "n8n:cache:workflow-project" "kJ0kaWZlHa3KwcWw" 1762987961.748456 [0 127.0.0.1:49304] "hget" "n8n:cache:project-owner" "aepvBToOYsssARgi" 1762987961.750307 [0 127.0.0.1:49304] "get" "n8n:cache:variables" 1762987961.750322 [0 127.0.0.1:49304] "hget" "n8n:cache:workflow-project" "kJ0kaWZlHa3KwcWw" 1762987971.013431 [0 127.0.0.1:49304] "get" "n8n:cache:variables" 1762987971.013536 [0 127.0.0.1:49304] "hget" "n8n:cache:workflow-project" "kJ0kaWZlHa3KwcWw" 1762987971.028417 [0 127.0.0.1:49304] "hget" "n8n:cache:workflow-project" "kJ0kaWZlHa3KwcWw" 1762987971.028977 [0 127.0.0.1:49304] "hget" "n8n:cache:project-owner" "aepvBToOYsssARgi" 1762987971.030609 [0 127.0.0.1:49304] "get" "n8n:cache:variables" 1762987971.030617 [0 127.0.0.1:49304] "hget" "n8n:cache:workflow-project" "kJ0kaWZlHa3KwcWw" 1762987976.797001 [0 127.0.0.1:49304] "get" "n8n:cache:variables" 1762987976.797039 [0 127.0.0.1:49304] "hget" "n8n:cache:workflow-project" "kJ0kaWZlHa3KwcWw"
Performance Monitoring of n8n with Queue Mode
After enabling Queue Mode in n8n, we can also monitor its performance. Below are several methods:
- Redis statistics:
redis-cli info stats
- Memory usage:
redis-cli info memory
- Checking the number of tasks in different states:
# Waiting
redis-cli llen "bull:n8n:wait"
# In progress
redis-cli llen "bull:n8n:active"
# Completed
redis-cli llen "bull:n8n:completed"
# Failed
redis-cli llen "bull:n8n:failed"
Additional n8n and FreeBSD Optimization
After configuring Queue Mode, we can also add some advanced optimizations for maximum performance of our installation.
Advanced n8n Optimization
To do this, we edit the file /home/n8n/.n8n/.env and add additional optimization variables at the end:
# ADVANCED OPTIMIZATIONS
# Increase payload limits
N8N_PAYLOAD_SIZE_MAX=16
N8N_DEFAULT_BINARY_DATA_MODE=filesystem
# Node.js functions (code nodes)
NODE_FUNCTION_ALLOW_BUILTIN=*
NODE_FUNCTION_ALLOW_EXTERNAL=*
# Workflow execution optimization
EXECUTIONS_TIMEOUT=3600
EXECUTIONS_TIMEOUT_MAX=7200
# Metrics and monitoring
N8N_METRICS=true
N8N_METRICS_PREFIX=n8n_
# Connection optimization
N8N_SKIP_WEBHOOK_DEREGISTRATION_SHUTDOWN=true
# Database settings (SQLite optimization)
DB_SQLITE_VACUUM_ON_STARTUP=true
DB_SQLITE_ENABLE_WAL=true
# Push notifications for UI
N8N_PUSH_BACKEND=websocket
# Diagnostics (disable in production if not needed)
N8N_DIAGNOSTICS_ENABLED=false
N8N_VERSION_NOTIFICATIONS_ENABLED=true
Variable overview:
N8N_PAYLOAD_SIZE_MAX=16– maximum size of data transferred between workflow nodes in MB. Default is 4 MB. Increasing to 16 MB allows processing larger datasets without errors.N8N_DEFAULT_BINARY_DATA_MODE=filesystem– stores binary data (files, images) on disk instead of in memory. This significantly reduces RAM usage when working with large files.NODE_FUNCTION_ALLOW_BUILTIN=*– allows use of all built-in Node.js modules in Function nodes. Increases flexibility when creating custom logic.NODE_FUNCTION_ALLOW_EXTERNAL=*– allows importing external npm packages in Function Code nodes. Powerful feature for advanced users.EXECUTIONS_TIMEOUT=3600– maximum workflow execution time (1 hour). Prevents “stuck” workflows.N8N_METRICS=true– exports metrics in Prometheus format at /metrics. Essential for monitoring with Prometheus/Grafana.DB_SQLITE_ENABLE_WAL=true– enables Write-Ahead Logging for SQLite. Significantly improves performance with concurrent operations.N8N_PUSH_BACKEND=websocket– uses WebSocket for real-time communication between server and UI. Faster than SSE.
Of course, after making changes, we restart n8n:
service n8n restart
Verifying FreeBSD System Limits for n8n User
The default FreeBSD installation does not impose limits on system resources available to users. However, it’s worth verifying this:
grep -A 30 "^default:" /etc/login.conf | grep -E "openfiles|maxproc|cputime|memoryuse"
:cputime=unlimited:\ :memoryuse=unlimited:\ :openfiles=unlimited:\ :maxproc=unlimited:\ :vmemoryuse=unlimited:\
However, if for some reason the FreeBSD installation has modified limits, we can restore them to unlimited or create a dedicated class for the n8n user by editing the file /etc/login.conf:
# File: /etc/login.conf
n8n:\
:cputime=unlimited:\
:memoryuse=unlimited:\
:openfiles=unlimited:\
:maxproc=unlimited:\
:vmemoryuse=unlimited:\
:tc=default:
In practice, “unlimited” doesn’t mean literally no limits. Kernel-wide system limits (sysctl) still apply. However, this is the best approach for production services, allowing the application to use as many resources as it needs within system capabilities.
After changes, we must rebuild the login database, converting the text login.conf file to the binary format used by the system:
cap_mkdb /etc/login.conf
Next, we assign the newly added n8n class to the n8n user:
pw usermod n8n -L n8n
To check the current limits for the running n8n process, execute the following command:
limits -P $(ps aux | grep '[n]8n' | awk '{print $2}')
Example output:
Resource limits (current): cputime infinity secs filesize infinity kB datasize 33554432 kB stacksize 524288 kB coredumpsize infinity kB memoryuse infinity kB memorylocked infinity kB maxprocesses 8499 openfiles 116964 sbsize infinity bytes vmemoryuse infinity kB pseudo-terminals infinity swapuse infinity kB kqueues infinity umtxp infinity pipebuf infinity kB
Diagnostics and Useful Commands
Check Redis status
redis-cli info stats
Check Redis queue length
redis-cli llen bull:n8n:jobs:waiting
Redis performance test
redis-cli --latency
Restart all services
service n8n restart
service nginx restart
service redis restart
service pf restart
Status of all services
service n8n status
service nginx status
service redis status
service pf status
Summary
In this guide, I tried to present all the necessary steps and information to run a fully functional, secure, and optimized n8n environment on FreeBSD. I use the identical configuration daily. The installation meets production standards and is ready to handle business tasks.
Key Features of the Presented Solution
Security
- PF firewall with rate limiting and automatic attack blocking
- SSL/TLS encryption with modern algorithms (TLS 1.3)
- Dedicated system user with limited privileges
- Multi-layer authentication (Nginx + n8n)
- Security headers protecting against XSS, clickjacking, and other attacks
- Nginx with Rate Limiting Zones
High Availability
- Automatic service restarts in case of failure
- Health checks and queue monitoring
- Easy restoration to previous states
Performance
- Redis cache for fast data access
- Optimized system limits
- Efficient task queuing
Ease of Management thanks to FreeBSD
- Centralized logs in one place
- Simple service commands for all components
- Automatic certificate renewal
- Clear structure of configuration files
I’ll be happy to answer questions in the comments.








Hello Marcin,
This guide is the best FreeBSD configuration tutorial I’ve ever read. I wasn’t even interested in n8n but now I am because of how well you have documented all of the necessary steps required to get n8n running, and exactly WHY they all of these steps are necessary.
This is not just an n8n tutorial but a comprehensive FreeBSD configuration guide on the pf firewall, node.js, Redis, Nginx, SSL certificates, etc.
Thanks for sharing your knowledge.!
Thank you very much for your appreciation. I try to write in a way that I myself would like to read similar tutorials. I sincerely recommend that you take a deeper interest in the subject of automation through n8n and AI – it is one of the key business and professional areas of today. Best regards!