How to Secure a Linux Server for Node.js (Beginner-Friendly, Step by Step)

Published: (December 15, 2025 at 04:51 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

🧠 Security Philosophy (Simple Explanation)

We follow three basic rules:

  • Never run apps as root
  • Each app gets its own user
  • The app can only access its own files

If an attacker breaks into your app, they should not be able to:

  • install crypto miners
  • modify system files
  • add users
  • affect other apps

Assumptions

  • Ubuntu 22.04 / 24.04
  • You can SSH into the server
  • You start as root (fresh cloud server)

Step 1: Create a Non-Root Admin User

Create a normal user for yourself (example: dev).

adduser dev

Give it a strong password, then allow this user to administer the system without logging in as root:

usermod -aG sudo dev

Why?

  • Root SSH access is dangerous
  • A normal user + sudo is safer and auditable

Step 2: Set Up SSH Key Login

On your local machine, generate an SSH key (if you don’t have one):

ssh-keygen -t ed25519

Copy the public key to the server:

mkdir -p /home/dev/.ssh
nano /home/dev/.ssh/authorized_keys   # paste your public key here

Fix permissions:

chown -R dev:dev /home/dev/.ssh
chmod 700 /home/dev/.ssh
chmod 600 /home/dev/.ssh/authorized_keys

Test from your computer:

ssh dev@YOUR_SERVER_IP

If this works, you’re safe to continue.

Step 3: Lock Down SSH (Very Important)

Edit the SSH config:

sudo nano /etc/ssh/sshd_config

Ensure these lines exist and are not commented:

PermitRootLogin no
PubkeyAuthentication yes

At the bottom of the file, add:

Match all
    PasswordAuthentication no

Reload SSH safely:

sudo systemctl reload ssh

What this does

  • Root SSH login ❌ disabled
  • Password login ❌ disabled
  • SSH keys ✅ required

💡 Cloud recovery consoles still work — you won’t get locked out.

Step 4: Enable the Firewall

Allow required ports before enabling:

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443

Enable the firewall:

sudo ufw enable

Check status:

sudo ufw status verbose

Why?

  • Blocks random internet scans
  • Only allows what you explicitly need

Step 5: Install Node.js (System‑Wide)

Install Node.js using the official repository (example: Node 24 LTS):

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs

Verify:

node -v
which node

Expected output:

  • /usr/bin/node owned by root

Why not nvm?

  • System services (systemd) don’t work well with nvm
  • Root‑owned Node is safer and predictable

Step 6: Create a Dedicated App User

Never run apps as your admin user. Create a service user (example: svc-nextjs):

sudo adduser \
  --system \
  --no-create-home \
  --group \
  --shell /usr/sbin/nologin \
  svc-nextjs

What this means

  • No SSH access
  • No shell
  • No sudo
  • Only exists to run the app

Step 7: Isolate App Files

Create a directory for your app:

sudo mkdir -p /var/apps/nextjs
sudo chown -R svc-nextjs:svc-nextjs /var/apps/nextjs
sudo chmod 750 /var/apps/nextjs

Test as admin (should fail):

cd /var/apps/nextjs

Test as service user (should succeed):

sudo -u svc-nextjs ls /var/apps/nextjs

Step 8: Run the App with systemd (Hardened)

Create a service file:

sudo nano /etc/systemd/system/nextjs.service

Paste the following:

[Unit]
Description=Next.js Application (Hardened)
After=network.target

[Service]
Type=simple
User=svc-nextjs
Group=svc-nextjs
WorkingDirectory=/var/apps/nextjs

ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/apps/nextjs
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
CapabilityBoundingSet=
AmbientCapabilities=
UMask=0077

[Install]
WantedBy=multi-user.target

Reload systemd:

sudo systemctl daemon-reload

Step 9: Check Security Score

Run:

systemd-analyze security nextjs.service

A score around 2–3 is excellent for a web service.

What this protects against

  • Privilege escalation
  • System file modification
  • Kernel abuse
  • Crypto miners
  • Lateral movement

Even if the app is hacked, the OS remains safe.

Step 10: Final Sanity Checks

Verify SSH configuration:

sudo sshd -T | egrep 'permitrootlogin|passwordauthentication|pubkeyauthentication'

Expected output:

permitrootlogin no
passwordauthentication no
pubkeyauthentication yes

Check firewall status:

sudo ufw status

What You Achieved

  • ✅ Root SSH disabled
  • ✅ Password login disabled
  • ✅ Key‑only access
  • ✅ Firewall enabled
  • ✅ Per‑app service users
  • ✅ Filesystem isolation
  • ✅ Strong systemd sandbox

This is real production security, not just theory.

Final Advice

  • One app = one service user
  • Never run apps as root
  • Let systemd enforce security
  • Keep SSH boring and locked down

You now have a secure Linux foundation for Node.js, Python, or any backend service.

Happy shipping 🚀

Welcome to proper server hardening.

Back to Blog

Related posts

Read more »