Skip to content
Marcin Szewczyk-Wilgan – Full-stack Designer
m4c
Marcin Szewczyk-Wilgan - Full-stack DesignerMarcin Szewczyk-Wilgan - Full-stack Designer
  • About me
  • Portfolio
  • Blog
  • Contact
Linkedin page opens in new windowFacebook page opens in new windowInstagram page opens in new windowGithub page opens in new window
  • About me
  • Portfolio
  • Blog
  • Contact

n8n on FreeBSD – installation, configuration, and optimisation

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 example n8n workflows running on a FreeBSD server set up according to this guide. The automated competitive analysis task uses OpenAI to process data retrieved through the Reddit API.

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 na FreeBSD - schemat ideowy rozwiązania uwzględniający instalację i konfigurację firewalla PF, Node.js, Redis, Nginx wraz z certyfikatem SSL.

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 TTL
    • noeviction – 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

 

Example n8n workflow running on FreeBSD server. Automated competitor analysis task uses Reddit discussions processed by Google Gemini AI.

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 endpoints
  • rate=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/login endpoint 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_header headers 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.

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.

 

Example n8n workflow running on FreeBSD server verifying the correctness of our installation.

Example n8n workflow running on FreeBSD server verifying the correctness of our installation.

 

 

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=1 we 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.

By Marcin Szewczyk-Wilgan2 Comments
Share this post
Share on FacebookShare on Facebook Share on LinkedInShare on LinkedIn Share on XShare on X Pin itShare on Pinterest Share on WhatsAppShare on WhatsApp

Author: Marcin Szewczyk-Wilgan

My name is Marcin Szewczyk-Wilgan and I work as a full-stack designer. I professionally do typography and publication design and visual communication. As a certified UX designer, I also create usable, high ranking websites and online stores based on WordPress and Woocommerce. In addition, I manage Linux and FreeBSD systems, mainly in the areas of hosting services, data storage, backup and security. contact@m4c.pl

I also recommend

grep, sed, awk - The Unix Text Processing Trinity
grep, sed, awk – The Unix Text Processing Trinity
27 February 2026
FreeBSD audio diagnostics and optimization
FreeBSD audio diagnostics and optimization
3 February 2026
Rawa Blues Festival
Rawa Blues Festival – Let the Blues Roll
24 October 2025
FreeBSD, bit-perfect, high-quality equalizer, real-time mode
FreeBSD and hi-fi audio setup: bit-perfect, equalizer, real-time
31 January 2025
Various advertisements and covers related to UNIX software, featuring text and graphics.
Advertisements of Unix and its technologies from 1984-1998
23 January 2025
Presonus iOne - inside
Inside the PreSonus Audiobox iOne
30 September 2019

2 Comments

  1. Kevin says:
    29 November 2025 at 07:46

    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.!

    Reply
    • Marcin Szewczyk-Wilgan says:
      29 November 2025 at 17:30

      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!

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *