Installing n8n on a RaspberryPi 3B+

29 Jul 2026 - tsp
Last update 29 Jul 2026
Reading time 13 mins

There is a certain class of advice that is always annoying me. In the case of n8n on a RaspberryPi 3B+, this usually sounds like: just use Docker (or do not bother at all because npm install will fail anyway). The problem with that advice is that it leaves a practical gap for the exact kind of setup where one wants to use older low-power boards in the first place but still wants to avoid to pull arbitrary strange binary containers from anywhere on the net. Docker containers are a really great deployment tool. But they are great then when you build them by yourself.

In my case these small machines are not used for highly reliable production infrastructure. They are used for background experiments, data collection from remote locations, light uncritical automation and all the odd jobs for which one does not want to waste a full workstation that produces more heat, more fan noise and of course more power consumption over 24/7. A RaspberryPi 5 with 4 GB of RAM is already sufficient for small local language model tasks such as llama3.2:3b, gemma3:1b or llama3.2-abliterated, and it also works well as a tiny embeddings server for mxbai-embed-large:latest when all one wants is local vector indexing and similarity search. The old RaspberryPi 3B+ is much weaker, but it is still perfectly usable for the control plane around such experiments.

I also prefer to keep the software stack uniform where possible. On these devices I run RaspberryPi OS because I want to manage both the Pi 5 and the Pi 3B+ with the same automation, and on the Pi 5 I currently need that operating system due to hardware support considerations. Otherwise I would very likely have chosen FreeBSD. Once one accepts that the Pi 3B+ is an lightweight machine and not a miracle worker, the question is no longer whether it is elegant to run n8n there. The question is simply whether it works well enough. The answer is yes, provided one handles memory carefully during installation.

As of today, the official n8n documentation still supports installation via npm and the documentation states that n8n requires a Node.js version between 20.19 and 24.x inclusive. So the npm route is not some unsupported hack. The real obstacle is memory pressure during install, not a fundamental incompatibility.

A nearly empty RaspberryPi Rack

Why the Pi 3B+ Is Annoying For NPM

The RaspberryPi 3B+ has only 1 GB of RAM. That is enough for many long-running low-intensity services, but it is not enough to comfortably install large modern JavaScript applications in the most naive way. The operating system itself already needs some memory, the package manager needs memory, the node JavaScript runtime needs massive loads of memory and the npm dependency tree of n8n is not exactly tiny.

The particularly annoying part is that on current RaspberryPi OS releases one often begins with zram-based swap or at least a swap setup that heavily prefers compressed RAM first. That is sensible for many workloads because it reduces wear on SD cards (if you dont do this you will experience them dying periodically), but it does not help enough when the actual problem is a large installation step that briefly needs more memory than the machine physically has. In that case you usually need a temporary real swap file with enough space to let the install finish at all.

This is one of those situations where I would rather accept a somewhat ugly one-time installation procedure than pretend the hardware is unusable. Once n8n is installed and configured conservatively, runtime memory usage is much easier to control than the installation peak.

Base System Preparation

I assume a plain RaspberryPi OS installation with SSH access already working.

I first updated the system and installed a few basic tools:

sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove -y
sudo apt clean

sudo apt install -y \
    git \
    curl \
    ca-certificates \
    build-essential \
    python3 \
    python3-venv \
    python3-pip \
    sqlite3 \
    rsync

For n8n itself I created a dedicated system user and a dedicated directory tree below /srv/n8n:

sudo adduser \
    --system \
    --group \
    --home /srv/n8n \
    --shell /bin/bash \
    n8n

sudo install -d -o n8n -g n8n -m 0750 /srv/n8n
sudo install -d -o n8n -g n8n -m 0750 /srv/n8n/data
sudo install -d -o n8n -g n8n -m 0750 /srv/n8n/bin
sudo install -d -o root -g n8n -m 0750 /etc/n8n

If you have a USB SSD or even a decent USB flash device available, it is better to place /srv/n8n/data there instead of on the SD card. The installation itself is already write-heavy enough; there is no need to additionally burn through the card with workflow history if one can avoid it.

Creating Temporary Real Swap

This is the step that makes the difference between a frustrating evening and a successful install. I created a temporary 2 GB swap file, enabled it with higher priority, and disabled the in-memory zram swap device for the duration of the installation:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon --priority 10 /swapfile
sudo swapoff /dev/zram0

If your system does not currently use /dev/zram0, simply skip the last line. The point is not the exact device name, the point is that during the installation I wanted the machine to rely on actual swap space instead of first trying to compress itself to death inside already scarce RAM.

This is not something I would necessarily keep forever. It is a pragmatic installation measure. After the system is running, you can decide whether to restore the previous swap arrangement, keep the swap file, or move swap to more suitable storage.

Installing Node.js and n8n with nvm

The official n8n npm installation path is straightforward. The tricky part on a RaspberryPi 3B+ is doing it in a memory-conscious way. I switched to the n8n user, installed nvm, selected Node.js version 22, reduced concurrency a bit and limited the JavaScript heap during install. Note that the following example shows piping an install script into bash. This is bad practice but follows official documentation. In reality first fetch the script, verify it’s content (read and understand it) and then perform the installation process.

sudo -u n8n -H bash

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash

export NVM_DIR="/srv/n8n/.nvm"
. "${NVM_DIR}/nvm.sh"

nvm install 22
nvm alias default 22
nvm use 22

# Verify installed versions
node --version
npm --version

export NODE_OPTIONS="--max-old-space-size=768"
export UV_THREADPOOL_SIZE=1
npm config set maxsockets 1

nice -n 19 ionice -c3 npm install --no-audit --no-fund --progress=false --global n8n

# Verify installed version
n8n --version
exit

There are two important remarks here.

First, the install can take a long time (read: many hours, let it running overnight) on this hardware. That is normal. This is not the moment to become impatient and interrupt it after half an hour. On my setup it finally succeeded overnight once sufficient real swap was available. Second, this does not contradict the common observation that the installation often fails on a RaspberryPi 3B+. It does fail if one simply starts from the default memory situation and hopes for the best.

Creating a Small Startup Wrapper

Because n8n was installed under nvm, I used a tiny wrapper script that sets the environment and then launches the service:

sudo tee /srv/n8n/bin/start-n8n >/dev/null <<EOF
#!/bin/bash
set -euo pipefail

export HOME=/srv/n8n
export NVM_DIR=/srv/n8n/.nvm

. "${NVM_DIR}/nvm.sh"
nvm use --silent default

exec n8n start
EOF

sudo chown n8n:n8n /srv/n8n/bin/start-n8n
sudo chmod 0750 /srv/n8n/bin/start-n8n

This is intentionally boring. Boring startup scripts are good startup scripts.

Environment Configuration

My runtime configuration lives in /etc/n8n/n8n.env:

N8N_USER_FOLDER=/srv/n8n/data

N8N_HOST=examplehost.example.net
N8N_PORT=5678
N8N_PROTOCOL=http

N8N_ENCRYPTION_KEY=replace-this-with-a-long-random-string

GENERIC_TIMEZONE=Europe/Vienna
TZ=Europe/Vienna

N8N_DIAGNOSTICS_ENABLED=false
N8N_VERSION_NOTIFICATIONS_ENABLED=true

EXECUTIONS_CONCURRENCY=2

EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
EXECUTIONS_DATA_PRUNE_MAX_COUNT=5000

EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
EXECUTIONS_DATA_SAVE_ON_ERROR=all
EXECUTIONS_DATA_SAVE_ON_PROGRESS=false

N8N_LOG_LEVEL=info

NODE_OPTIONS=--max-old-space-size=512

The two settings that matter most for a small machine are EXECUTIONS_CONCURRENCY=2 and the reduced heap size in NODE_OPTIONS. They reflect the fact that this is not a large multi-user automation cluster. It is a tiny background worker for experiments and light workflows.

If you migrate an existing .n8n directory from another machine, remember that the N8N_ENCRYPTION_KEY has to match the one used previously or existing credentials will not decrypt correctly. That detail is easy to overlook and mildly irritating when one does.

Depending on your workflows, you may also need additional settings:

Systemd Service

The following unit file worked well for me:

[Unit]
Description=n8n Workflow Automation
Documentation=https://docs.n8n.io/
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=n8n
Group=n8n

WorkingDirectory=/srv/n8n
EnvironmentFile=/etc/n8n/n8n.env

ExecStart=/srv/n8n/bin/start-n8n

Restart=on-failure
RestartSec=10
TimeoutStopSec=120
KillSignal=SIGTERM

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/n8n

UMask=0077

[Install]
WantedBy=multi-user.target

Save this as /etc/systemd/system/n8n.service, then reload systemd:

sudo systemctl daemon-reload

The hardening options above are still simple enough not to become a maintenance burden. At the same time they avoid the all-too-common habit of running everything with broad filesystem access just because it is convenient during the first test.

First Start and Autostart

Once the installation has completed, the service can be started and enabled:

sudo systemctl enable --now n8n

If you want to inspect the logs immediately afterwards:

journalctl -u n8n -n 100 --no-pager

If the service is not starting, check the obvious things first:

I also strongly recommend a full reboot test immediately after initial setup:

sudo reboot

Then reconnect via SSH and confirm:

systemctl status n8n

If it survives a reboot, the machine is usually good enough for the kind of low-intensity background usage I described above.

Notes on Data, SD Cards and Security

A setup like this is useful, but one should remain honest about its limitations.

The primary weakness is still the storage medium. SD cards are not ideal for persistent databases and long-running write-heavy services. n8n is manageable because one can reduce retained execution data aggressively, move the user folder to external storage and treat the whole machine as somewhat disposable infrastructure. Regular backups are not optional here. They are part of the design.

The second weakness is that SQLite is perfectly fine for a small one-user instance, but this is not the machine on which I would build a bigger queue-based automation platform. The official n8n documentation still uses SQLite by default for self-hosted installs, which is entirely reasonable for this sort of tiny deployment. It just means one should keep expectations aligned with the hardware.

The third weakness is operational temptation. Once n8n is running on a cute little low-power board, it becomes very easy to keep attaching more things to it until it quietly becomes important infrastructure. I would avoid that. My use case is deliberately modest: data mining, automation glue, background experiments, and some integration work around local or remote AI systems. When the machine dies, it should be annoying, not catastrophic.

Conclusion

Installing n8n on a RaspberryPi 3B+ via npm is entirely possible. The part that usually fails is not conceptual compatibility but memory pressure during installation. With a temporary real swap file, a dedicated service account, a conservative runtime configuration and a little patience, the old board is still useful.

I would not recommend this as the default path for everyone, and I definitely would not recommend it for serious production use. But if you already have a RaspberryPi 3B+, want a tiny always-on workflow engine and are comfortable accepting the limits of 1 GB RAM and SD-card-backed storage, there is no need to pretend that only Docker on larger hardware counts as a valid solution.

In my opinion that is exactly where these small machines still shine: not as replacements for proper servers, but as cheap, quiet and sufficiently capable companions for the slightly strange experimental ecosystems many of us accumulate over time.

References

This article is tagged:


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplainsQu98equt9ewh@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support