Home/Blogs/Linux 101: Architecture, Permissions, Processes, and Production Services

Linux 101: Architecture, Permissions, Processes, and Production Services

21 min read4 views

Most cloud workloads, Docker containers, Kubernetes nodes, and CI/CD runners run on Linux. If you build web backends, configure reverse proxies, or troubleshoot production servers when things catch fire, you need to know how Linux actually works under the hood.

Here is a breakdown of the core concepts, from low-level kernel abstractions to deploying and supervising production apps.

The Four Layers of Computing

Every computer system breaks down its work across four distinct layers:

  1. Applications (User Space): Your daily tools and runtimes like Node.js, Python, PostgreSQL, Nginx, Docker, and Bash. They run in unprivileged CPU mode (Ring 3) with isolated virtual memory addresses. If your app crashes with an unhandled exception, it only kills that one process without taking down the entire machine.
  2. Shell / CLI: The interface between you and the operating system. The terminal window is just a screen display capturing keystrokes and rendering text. The shell (Bash, Zsh, Fish) is the order taker: it parses your input, expands variables, sets up pipelines, and asks the kernel to do the real work.
  3. Linux Kernel: The core of the operating system running in privileged supervisor mode (Ring 0). Linux is specifically the kernel, not the whole OS. It manages physical hardware, schedules CPU time across active threads, maps memory pages, and handles filesystems.
  4. Physical Hardware: The silicon doing the actual work: CPU cores, RAM modules, NVMe storage, network cards, and GPUs.

Think of it like a restaurant: the terminal is the front door, the shell is the order taker, and the kernel is the kitchen manager allocating ingredients and stove time:

  • User Space (Ring 3): Applications run isolated with no direct hardware access
  • Shell & Stdio: Parses command strings and manages standard I/O streams
  • Kernel (Ring 0): Manages CPU scheduling, memory pages, VFS, and drivers
  • Hardware: Physical silicon executing instructions and handling interrupts
User Space (Node.js, Python, Nginx, CLI Tools)
Shell & Terminal (Bash, Zsh, Stdio)
System Call Interface (open, read, write, fork, socket)
Kernel Space (Scheduler, VFS, Memory, Drivers)
Physical Hardware (CPU, RAM, Storage, NIC, GPU)

Distributions like Ubuntu, Debian, Fedora, and Arch bundle the Linux kernel with GNU command-line utilities, systemd, package managers, and precompiled libraries into a complete, bootable operating system.

Privilege Levels and Diagnostics

The prompt character at the end of your shell line tells you your current privilege level:

  • $ means you are running as a normal unprivileged user.
  • # means you are logged in as root with unrestricted power.

Keep your daily work and development on a normal account with the $ prompt so an accidental rm or bad script doesn't wipe out system files.

Quick commands to check your session and host properties:

whoami
# Output: srbh

uptime
# Shows how long the server has been up, active users, and 1, 5, 15 min load averages:
# 15:10:36 up 1 day, 14:37, 1 user, load average: 1.06, 0.79, 1.04

hostnamectl
# Static hostname: srbh-Nitro
# Operating System: Ubuntu 24.04 LTS
# Kernel: Linux 6.8.0-generic
# Architecture: x86-64

Filesystem Architecture and Navigation

In Windows, storage is split into drive letters like C:\ and D:\. In Linux, everything starts from a single root directory: /.

Think of / as the main entrance to a giant shopping mall: every shop, utility closet, storage room, and corridor lives somewhere inside this single tree.

/ (Root)Binaries & Libraries/bin · /sbinCore Executables/lib · /usrShared LibrariesConfiguration & Boot/etcHost Configuration/bootKernel & BootloaderUsers & Operational/home · /rootUser & Root Homes/var · /tmpLogs & ScratchpadVirtual VFS & Devices/proc · /sysKernel State & Drivers/dev · /optDevice Nodes & Packages
Directory PathFHS Role & Purpose
/Root Directory
Base of the unified virtual filesystem; primary mount point
/binUser Binaries
Essential command binaries for all users (ls, cp, bash)
/sbinSystem Binaries
Administrative binaries for host maintenance (reboot, iptables)
/etcHost Configuration
Host-specific plain-text configuration files and service rules
/homeUser Data
Personal storage lockers and dotfiles for regular user accounts
/rootSuperuser Home
Isolated home directory for root on the primary system partition
/devDevice Nodes
Hardware device endpoints (/dev/sda, /dev/null, /dev/urandom)
/procKernel & Process VFS
RAM-backed virtual filesystem exposing active processes and kernel state
/sysHardware Subsystems
Virtual filesystem exposing the kernel device driver model
/varVariable Data
Dynamic operational data: logs (/var/log), databases, and spools
/tmpTemporary Scratchpad
Volatile storage cleared on reboot; world-writable with sticky bit
/usrUser Hierarchy
Secondary read-only hierarchy for applications, libraries, and tools

The Directory Layout

Linux distributions follow the Filesystem Hierarchy Standard (FHS):

  • /bin: Essential user command binaries (ls, cp, cat, bash). Modern distros symlink this directly to /usr/bin.
  • /sbin: System admin binaries meant for root maintenance and rescue (iptables, fdisk, reboot, ip).
  • /boot: Static files needed to boot the machine: GRUB bootloader files, RAM disk images (initrd), and kernel images (vmlinuz).
  • /dev: Device nodes representing physical and virtual hardware (/dev/nvme0n1, /dev/null, /dev/urandom).
  • /etc: Plain-text system configuration files (/etc/passwd, /etc/hosts, /etc/nginx/).
  • /home: Personal storage lockers for regular users (/home/srbh), holding code repos, user dotfiles, and app data.
  • /root: The private home directory of the root user. Stored directly on the root partition so it works even if other disks fail to mount.
  • /lib and /lib64: Shared libraries (.so) needed by binaries in /bin and /sbin.
  • /media and /mnt: Mount points for removable media (USB drives) and temporary manual mounts.
  • /opt: Add-on third-party software packages installed outside standard repos.
  • /proc: A virtual pseudo-filesystem created in RAM by the kernel. Shows live kernel stats and process data (/proc/cpuinfo, /proc/meminfo, /proc/1/).
  • /sys: Virtual filesystem exposing device drivers, hardware buses, and power states.
  • /srv: Site-specific data served by system services.
  • /tmp: Temporary scratchpad space. Any user can write here, and files are wiped on reboot.
  • /usr: Read-only user utilities, libraries, and docs (/usr/bin, /usr/lib, /usr/local).
  • /var: Variable data that grows during runtime: log files (/var/log), database records (/var/lib), and mail spools.

Absolute vs. Relative Paths

Paths tell the shell where to find a file:

  • Absolute path: The full path starting from root /. Example: /var/log/nginx/access.log.
  • Relative path: The path starting from wherever you are right now. If you are already inside /var/log, typing nginx/access.log points to the exact same file.

Quick navigation shortcuts:

  • . means current directory.
  • .. means parent directory.
  • ~ means current user's home directory (/home/srbh).
  • - means previous directory (cd - jumps back to where you just were).

Everything is a File

Unix systems treat nearly all system resources as streams of bytes. Plain files, folders, hardware sensors, and network pipes all respond to standard open(), read(), write(), and close() operations.

Files generally fall into three types:

  1. Regular Files: Normal files holding text, code, or compiled binaries. Marked with - in ls -l.
  2. Directories: Special files that list filenames and point them to inode numbers. Inodes hold the file metadata (permissions, owner, size, timestamps, disk block pointers) without holding the name itself. Marked with d in ls -l.
  3. Special Files:
    • Character devices: Raw byte streams without caching (like /dev/tty, keyboards, mice). Marked with c.
    • Block devices: Fixed-size block storage with hardware caching (like /dev/nvme0n1, /dev/sda). Marked with b.
    • Hard links: Multiple names pointing to the same inode on the disk. Deleting one name keeps the underlying data intact until every link to that inode is removed.
    • Soft links (symlinks): Shortcut files pointing to another path. If you delete the original file, the symlink breaks. Marked with l.
    • Sockets: Two-way communication endpoints used by local processes to talk to each other without network overhead. Marked with s.
    • Named pipes (FIFOs): One-way pipes that feed output from one process into another through the filesystem. Marked with p.

Everyday File Commands

# Print current directory
pwd

# List files with details and hidden dotfiles
ls -la

# Change directory
cd /var/www/html

# Go up one folder
cd ..

# Jump back to last directory
cd -

# Create nested directories in one go
mkdir -p proj/devops/prac

# Create an empty file or touch modified timestamp
touch index.js

# Print file contents
cat index.js

# Scroll through large files (press q to quit)
less access.log

# Overwrite file with new text
echo "PORT=3000" > .env

# Append text to the end of a file without overwriting
echo "NODE_ENV=production" >> .env

# Copy a directory recursively
cp -r src/ backup/

# Move or rename a file
mv server.js dist/server.js

# Delete a folder and its contents
rm -rf temp/

# Inspect environment variables
printenv

Users, Groups, and Permissions

Security in Linux centers on ownership. Every process running on the system and every file stored on disk belongs to a user and a group.

Accounts and /etc/passwd

User account info is stored in /etc/passwd. Each line gives the username, password placeholder, numeric User ID (UID), primary Group ID (GID), home directory, and default shell:

grep srbh /etc/passwd
# Output: srbh:x:1000:1000:Developer,,,:/home/srbh:/bin/bash

Accounts fall into three groups:

  • Root (superuser): Has UID 0. Bypasses permission checks and can read, edit, or delete anything on the machine.
  • System / Service accounts: Usually have UIDs between 1 and 999 (like nginx, postgres, sshd). Created automatically by packages to run background daemons with minimal permissions. They use /usr/sbin/nologin or /bin/false as their shell so nobody can SSH into them directly.
  • Normal user accounts: UIDs of 1000 and above. Created for regular human developers.

Group definitions live in /etc/group. A user has one primary group and can belong to multiple secondary groups.

To see who is currently logged into the server, use who. To see past login sessions and recent reboots, use last:

who
# srbh pts/0 2026-10-23 14:10 (192.168.1.50)

last -n 3
# Shows recent login and reboot timestamps

Sudo and Administrative Tasks

Logging in directly as root is dangerous. Instead, run admin commands through sudo (superuser do) from a normal account.

The /etc/sudoers file defines who can run sudo commands. Always edit this file with visudo because it checks syntax before saving and prevents you from locking yourself out:

# Add a user to the sudo group on Ubuntu/Debian
sudo usermod -aG sudo srbh

If a command fails because you forgot sudo, run sudo !!. It takes your last executed command and reruns it with sudo prepended:

systemctl restart nginx
# Output: Access denied.

sudo !!
# Runs: sudo systemctl restart nginx

Reading ls -l Permissions

Run ls -l on any file to see its permission string:

-rwxr-xr-- 1 srbh developers 4096 Oct 23 15:30 deploy.sh

Breaking down those first ten characters:

  • Character 1 is the file type (- for regular file, d for directory, l for symlink).
  • Characters 2 to 4 (rwx) are permissions for the file Owner (User).
  • Characters 5 to 7 (r-x) are permissions for the Group.
  • Characters 8 to 10 (r--) are permissions for Others (everyone else on the system).
Anatomy of a File Attribute String (ls -l)
-
TypeRegular
rw-
Owner (u)4 + 2 + 0 = 6
r--
Group (g)4 + 0 + 0 = 4
r--
Others (o)4 + 0 + 0 = 4
Combined Octal Command: chmod 644 index.html

Permissions use binary values that add up to octal numbers:

  • Read (r): Value 4. Read the file contents. On directories, allows running ls to see what is inside.
  • Write (w): Value 2. Edit or delete the file. On directories, allows creating, renaming, or removing files.
  • Execute (x): Value 1. Run a script or binary. On directories, allows you to cd into the folder and access nested files.
  • No permission (-): Value 0.

Add the numbers together to get common permission modes:

  • 644 (-rw-r--r--): Standard file mode. Owner can read and write (4+2=6); Group and Others can only read (4). Use this for HTML, CSS, configs, and static assets.
  • 755 (-rwxr-xr-x): Standard executable and directory mode. Owner has full permissions (7); Group and Others can read and execute (5). Needed on directories so users can enter them and list files.
  • 600 (-rw-------): Private secret mode. Only the owner can read and write (6); everyone else gets zero access (0). Used for SSH private keys (~/.ssh/id_ed25519) and .env files.
  • 700 (drwx------): Private directory mode. Only the owner can enter and read (7). Standard for ~/.ssh directories.
  • 777 (-rwxrwxrwx): World-writable mode. Gives every single account on the machine full read, write, and execute permissions.

Never use chmod 777 to fix permissions issues in production. If an app or daemon gets compromised, an attacker can overwrite your binaries or inject malicious scripts anywhere.

# Set standard file permissions
chmod 644 index.html

# Make a script executable
chmod 755 deploy.sh

# Lock down SSH keys
chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh

# Change owner to deploy user and group to developers
sudo chown deploy:developers /var/www/app

# Change ownership recursively across an entire folder
sudo chown -R deploy:developers /var/www/app

Package Management

Instead of compiling source code by hand, Linux distributions package software into precompiled archives with binaries, default configs, and dependency lists (.deb on Debian/Ubuntu, .rpm on Fedora/RHEL).

Think of your package manager like an app store for the operating system: it handles installs, updates, removals, and verifies cryptographic GPG signatures so you do not download tampered software.

Low-Level vs. High-Level Tools

  • Low-level tools (dpkg, rpm): Install packages from a local file on disk. They do not talk to remote servers or fix missing dependencies. If app A needs library B, they stop and complain.
  • High-level tools (apt, dnf): Smart assistants. They read your repository list (/etc/apt/sources.list), fetch missing dependencies from the cloud, and install everything in one go.

Common APT commands on Ubuntu and Debian:

# Update repository package lists
sudo apt update

# Upgrade installed packages to their newest versions
sudo apt upgrade -y

# Install packages along with their dependencies
sudo apt install -y nginx curl git

# Remove an app but keep its config files in /etc
sudo apt remove nginx

# Completely wipe an app and its configs
sudo apt purge nginx

# Clean up unused dependencies left behind by uninstalled apps
sudo apt autoremove -y

# Search for a package by name or description
apt search redis-server

# Show package details, size, and dependencies
apt show redis-server

Processes, Signals, and Job Control

Files on disk are static data stored on an SSD (nouns). Processes are running instances of programs loaded into RAM and using CPU cycles (verbs). A single binary file on disk can launch multiple independent running processes.

Process Identity and PID 1

Every running process has:

  • PID (Process ID): A unique number tracking the process.
  • PPID (Parent Process ID): The PID of the process that started it.
  • User and Group: Decides what files the process can read and write.
  • Resource Usage: Percentage of CPU cores and RAM (RES) being used.

When Linux boots up, the kernel initializes hardware and starts one single user-space process: PID 1. On modern systems, PID 1 is systemd. Every other service, background daemon, and shell session descends from this parent process.

Process States and Signals

The kernel scheduler tracks process states:

Process Lifecycle State Machine
fork()I/O waitwakeupCtrl+ZSIGCONTexit()wait()Reaped[ S / D ] SleepingWaiting on Event / I/O[ R ] RunningActive on CPU[ T ] StoppedSuspended by Signal[ Z ] ZombieDefunct State
RRunning
SInterruptible Sleep
DDisk Sleep
TStopped
ZZombie

Common states you see in ps or top:

  • R (Running / Runnable): Actively running on a CPU core or waiting in the scheduler queue for a time slice.
  • S (Interruptible Sleep): Idle, waiting for an event, timer, or network packet. Wakes up as soon as a signal arrives.
  • D (Disk / Uninterruptible Sleep): Blocked waiting on hardware I/O (like reading from disk). Cannot be killed, even with SIGKILL, until the hardware operation completes.
  • T (Stopped): Paused by a signal like Ctrl+Z or suspended under a debugger.
  • Z (Zombie): Finished execution, but its exit code has not been read by its parent process via wait(). Memory is freed, but the PID remains in the process table.

Linux uses POSIX signals to communicate with running processes:

  • SIGTERM (Signal 15): A polite request to stop. The process catches this signal, finishes pending work, closes database connections, and exits cleanly. Always try SIGTERM first.
  • SIGKILL (Signal 9): Immediate emergency shutdown. The kernel terminates the process instantly without giving it a chance to save files or close connections. Use this only when a process is frozen.
  • SIGHUP (Signal 1): Hangup signal. Sent when a terminal disconnects. Daemons like Nginx catch SIGHUP to reload configuration files without restarting.
  • SIGINT (Signal 2): Interrupt signal generated by pressing Ctrl+C in your terminal to cancel a foreground command.
# Polite shutdown request (SIGTERM)
kill 4520

# Force immediate kill if stuck (SIGKILL)
kill -9 4520

# Reload config without dropping traffic (SIGHUP)
kill -HUP 1240

# Kill processes by name
killall node
pkill -f "python3 app.py"

Process Monitoring

Tools to check running processes:

# Snapshot all active processes across all users
ps aux

# Filter running processes with grep
ps aux | grep node

# Built-in live process monitor
top

# Interactive, color-coded monitor with core meters
htop

In ps aux, check the TTY column: a ? means the task is a background service running detached from any terminal.

In htop, press F4 to filter by name, F6 to sort by memory or CPU usage, and F9 to send kill signals.

Foreground vs. Background Tasks

Running a command normally locks your shell prompt until it finishes, like waiting on an OTP screen:

  • Add & to run a command in the background immediately:
    python3 long_task.py &
  • Press Ctrl+Z to freeze a running foreground task and put it into Stopped (T) state.
  • Type bg to let that frozen task resume running in the background.
  • Type fg to bring a background job back to the foreground.
  • Type jobs to list background tasks running in your current shell.

If your SSH session drops, your shell closes and sends SIGHUP to all child processes spawned during that session, killing them. To keep long-running tasks alive when you disconnect, use a terminal multiplexer like tmux, or manage them with systemd or PM2.

Systemd Service Management

Running apps inside open terminal windows does not survive server reboots or crashes. Modern Linux uses systemd to keep background services running reliably.

Anatomy of a Unit File

Service configuration files live in /etc/systemd/system/<service-name>.service. A standard unit file has three sections:

/etc/systemd/system/myapp.service
Unit File
[Unit]
Description=Production Node.js API Service
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node /var/www/myapp/server.js
Restart=always
RestartSec=5s
EnvironmentFile=/var/www/myapp/.env
[Install]
WantedBy=multi-user.target
  • [Unit]: Gives the service a description and sets startup ordering. After=network.target makes sure network interfaces are online before your app starts.
  • [Service]: Defines what to run. ExecStart must use the full absolute path to your binary. User makes sure the app runs as a restricted service account, not root. Restart=always automatically revives the app if it crashes, and RestartSec=5s prevents endless crash loops from pinning the CPU at 100%.
  • [Install]: Tells systemd when to launch the service. WantedBy=multi-user.target starts the app automatically during standard server boot.

Managing Services with systemctl

Manage your services with systemctl:

# Tell systemd to reload config files after editing a unit file
sudo systemctl daemon-reload

# Start a service right now (will not persist across reboot)
sudo systemctl start myapp

# Set a service to start automatically on boot
sudo systemctl enable myapp

# Enable for boot AND start immediately in one command
sudo systemctl enable --now myapp

# Check status, uptime, PID, memory, and recent logs
systemctl status myapp

# Restart a service
sudo systemctl restart myapp

# Instantly list all broken or crashed services on the system
systemctl --failed

Centralized Logs with journalctl

Systemd routes all stdout and stderr logs through the journald daemon, so you don't need messy nohup.out files:

# Stream live logs in real time (like live delivery tracking)
journalctl -u myapp -f

# View the last 50 log lines
journalctl -u myapp -n 50

# View all logs since the current boot
journalctl -b

# View logs from the past hour
journalctl -u myapp --since "1 hour ago"

# View error logs with full failure details
journalctl -xeu myapp

Production Server Hosting and CI/CD

Deploying an application to a production Linux VM coordinates four foundational layers:

  1. UFW Firewall: Closes unused ports and leaves only SSH (22), HTTP (80), and HTTPS (443) open to the public internet.
  2. Caddy Reverse Proxy: Sits at the public edge, handles automatic SSL/TLS certificates for your custom domain, and proxies traffic locally to internal application ports (http://127.0.0.1:3000).
  3. PM2 / Systemd: Process manager keeping your application alive across CPU cores with auto-restart on crashes.
  4. GitHub Actions CI/CD: Pulls updated code on git push and reloads the application over SSH without downtime.

UFW Firewall Setup

Never expose internal database ports or raw application ports directly to the internet. Use UFW (Uncomplicated Firewall) to filter incoming traffic:

# Deny all incoming connections, allow all outgoing connections
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH access
sudo ufw allow ssh

# Allow HTTP and HTTPS traffic
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Turn on the firewall
sudo ufw enable

# Check firewall status and active rules
sudo ufw status verbose

DNS Records: Pointing Your Domain to the Server

Before configuring your reverse proxy, your domain's DNS records must point to your server's public IP address. In your DNS provider (Cloudflare, Namecheap, Route53, GoDaddy, etc.), create:

  • A record: Set the name to @ (for root domain like yourdomain.com) or api (for subdomain like api.yourdomain.com) and the value to your server's public IPv4 address (e.g. 203.0.113.10).
  • AAAA record (optional): Set to your server's IPv6 address if your server has one assigned.
  • Proxy status: If using Cloudflare, set the record to "DNS only" (grey cloud) during initial SSL setup so Caddy can perform direct ACME challenges without proxy interference.

Verify that the DNS record has propagated from your terminal using dig:

dig +short api.yourdomain.com
# Output should return your server IP: 203.0.113.10

Caddy Reverse Proxy & Automatic SSL

Traditional servers like Nginx require manual Certbot cron jobs, Diffie-Hellman parameter generation, and 30-line config files.

Caddy is a modern web server written in Go that has automatic HTTPS by default. The moment you point your domain to your server and define your domain in a Caddyfile, Caddy automatically requests certificates from Let's Encrypt or ZeroSSL, provisions TLS, and handles 90-day auto-renewals with zero maintenance.

Install Caddy on Ubuntu/Debian:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

Configuring the Caddyfile

Caddy's configuration file lives at /etc/caddy/Caddyfile. A full reverse proxy with automatic HTTPS, HTTP-to-HTTPS redirects, and WebSocket support takes only three lines:

api.yourdomain.com {
    reverse_proxy localhost:3000
}

If you are hosting a static frontend along with an API on the same VPS, you can define multiple domain blocks in the same Caddyfile:

# Reverse proxy backend API to local port 3000
api.yourdomain.com {
    reverse_proxy localhost:3000
}

# Serve compiled frontend build from disk
yourdomain.com {
    root * /var/www/html/dist
    file_server
    try_files {path} /index.html
}

Validating and Reloading Caddy

Validate your configuration syntax before applying it, then reload the Caddy systemd daemon:

# Validate Caddyfile syntax
caddy validate --config /etc/caddy/Caddyfile

# Reload Caddy without dropping active connections
sudo systemctl reload caddy

# View live Caddy logs and certificate issuance
journalctl -u caddy -f

The moment Caddy starts, it verifies your DNS record, fetches an SSL certificate from Let's Encrypt via ACME challenge, and begins proxying encrypted HTTPS traffic to your local port 3000.

With Caddy handling SSL at the edge and proxying traffic internally to http://127.0.0.1:3000, the next step is keeping your backend process running reliably on that local port.

Running Node.js with PM2

Running node server.js inside an SSH terminal stops the moment you disconnect or your code hits an unhandled error. For Node.js production backends, PM2 manages process clustering across CPU cores, automatic restarts, and log rotation:

# Install PM2 globally
sudo npm install -g pm2

# Navigate to your project root
cd /var/www/myapp

# Start application in cluster mode across all available CPU cores
pm2 start dist/server.js --name "api" -i max

# Monitor running apps, uptime, memory, and restart counts
pm2 list

# Stream live application logs in real time
pm2 logs api --lines 50

# Save active process list to disk
pm2 save

# Generate and configure the systemd startup hook for PM2
pm2 startup

When you run pm2 startup, PM2 generates a custom sudo env PATH=... command. Copy and run that command in your shell. This registers PM2 directly with systemd so your entire application cluster revives automatically whenever the Linux VM boots.

Automated CI/CD with GitHub Actions

Instead of manually SSHing into the host on every code change, a GitHub Actions workflow automates tests, build steps, and rolling reloads:

name: Deploy to Production Linux VM

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: deploy
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            set -e
            cd /var/www/myapp
            git pull origin main
            npm install --omit=dev
            npm run build
            pm2 reload api --update-env

The pm2 reload api --update-env command performs a zero-downtime rolling reload: it replaces cluster worker processes one by one so active client requests are never interrupted.

Server Hardening Checklist

Before routing live production traffic, secure the SSH daemon configuration in /etc/ssh/sshd_config:

  1. Disable root logins over SSH:
    PermitRootLogin no
  2. Disable password authentication so only cryptographic SSH keys can authenticate:
    PasswordAuthentication no
  3. Test SSH daemon syntax and reload the service:
    sudo sshd -t
    sudo systemctl restart sshd
  4. Turn on automatic security patches so the Linux kernel and packages update themselves:
    sudo apt install -y unattended-upgrades
    sudo dpkg-reconfigure --priority=low unattended-upgrades

With UFW filtering ports, Caddy terminating SSL at the network edge, PM2 clustering your app across CPU cores, and GitHub Actions deploying updates with zero downtime, your production setup is clean, predictable, and resilient.

Wrap-up

Getting comfortable with Linux comes down to building intuition around core primitives: the four computing layers, the unified root filesystem, user and group permissions, process signals, and systemd service management.

These notes summarize my personal learnings from this Linux tutorial on YouTube. If you want to follow along with the video walkthrough, check out the tutorial directly on YouTube.

Designed & Developed by Saurabh

© 2026 All rights reserved.

Visitors#17,546

Jaipur, India --:-- -- IST