Essential Linux Server Commands for Deployment

Devops & Infrastructure and Tips & Tricks

Essential Linux Server Commands for Deployment

Every deploy ends up asking the same questions of the target server. Is there enough disk? Did the old release get cleaned up? Why is memory pinned at 95%? Which process is holding port 3000? Where do the logs live for the thing that just crashed?

This guide is the working set of Linux commands that come up in those moments — disk and memory inspection, process and port inspection, log reading, and a few file-system tools that earn their keep on every deploy. It's the practical companion to Linux distros for deployment and checking your Ubuntu version — once you know what server you've got, these are the commands you'll actually type.

Disk space — the most common pre-deploy check

A deploy that runs out of disk fails in unpredictable ways. The first thing to check on any server you don't fully trust:

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   38G   10G  79% /
tmpfs           2.0G   12M  2.0G   1% /run
/dev/sda2       200G  142G   49G  75% /var

-h prints sizes in human-readable form (G, M, K). The Use% column is the one to watch — anything over 80% is worth investigating before pushing a deploy that copies a few hundred MB of release files.

For an inode check (separate from disk space — large numbers of small files can exhaust inodes before bytes run out):

df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/sda1      3276800 245031 3031769    8% /

If IUse% is high but Use% is low, you have an inode shortage — usually a sign that a directory is filling up with millions of small files (sessions, cache fragments, runaway log rotation).

Find what's taking up the space

df tells you the partition is 79% full. du tells you which directory is responsible:

sudo du -h --max-depth=1 /var | sort -hr | head -20
142G    /var
112G    /var/log
24G     /var/lib/docker
3.2G    /var/cache
1.8G    /var/lib/postgresql

--max-depth=1 keeps the output manageable; sort -hr puts the biggest items first. Drill down one level at a time:

sudo du -h --max-depth=1 /var/log | sort -hr | head -10

The pattern repeats — re-run with deeper paths until you find the actual offender. The most common culprits on a deploy server: old release directories that weren't cleaned up, oversized application log files, Docker images and overlay layers, and database backup directories that haven't rotated.

For a one-shot find the biggest files anywhere on disk:

sudo find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | sort -k5 -hr | head -20

The 2>/dev/null suppresses permission-denied noise from system directories you can't read.

Memory — what's using RAM right now

free -h
              total        used        free      shared  buff/cache   available
Mem:           7.7Gi       4.2Gi       312Mi        88Mi       3.2Gi       3.1Gi
Swap:          2.0Gi          0B       2.0Gi

The column to watch is available, not free. free excludes the buff/cache memory that Linux uses as a disk cache — that memory is available to applications when needed; it just isn't sitting idle. available is the honest number.

If available is small and swap is being used, the server is genuinely memory-pressured. If available is small but swap is untouched, the kernel is using RAM for cache the way it should be.

For a per-process view of memory:

ps aux --sort=-%mem | head -10
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
postgres   843  0.1 18.4 8120324 1466240 ?     Ss   Apr12  12:34 postgres: main
www-data  1402  2.1 12.1 1543216  963548 ?     S    14:23   2:31 php-fpm: pool www
mongodb   2103  0.8  8.7 4321076  697432 ?     Ssl  Mar01  84:12 mongod

RSS (Resident Set Size) is the actual physical memory the process is holding, in KB. %MEM is that as a percentage of total RAM. The processes at the top of this list are the ones to investigate first when memory is tight.

CPU and processes

top is the live view; htop (sudo apt-get install -y htop) is the friendlier version. Both update in real time and show CPU per process.

For a one-shot snapshot in a script:

ps aux --sort=-%cpu | head -10

To find a specific process by name:

ps -ef | grep nginx | grep -v grep
www-data    1812  1810  0 14:20 ?        00:00:01 nginx: worker process
www-data    1813  1810  0 14:20 ?        00:00:01 nginx: worker process
root        1810     1  0 14:20 ?        00:00:00 nginx: master process /usr/sbin/nginx

grep -v grep filters out the grep command itself, which would otherwise match its own search pattern.

For just the PIDs (useful in scripts):

pgrep -f "nginx: worker process"

To kill a process by name:

sudo pkill -TERM nginx        # graceful — sends SIGTERM
sudo pkill -KILL nginx        # ungraceful — sends SIGKILL (use only after TERM doesn't work)

Ports and network connections

Knowing what's listening on which port is the second-most-common deploy-time question after disk space. The modern command is ss:

sudo ss -tlnp
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN  0       4096    0.0.0.0:22          0.0.0.0:*          users:(("sshd",pid=1234))
LISTEN  0       511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=1810))
LISTEN  0       511     0.0.0.0:443         0.0.0.0:*          users:(("nginx",pid=1810))
LISTEN  0       128     127.0.0.1:3000      0.0.0.0:*          users:(("node",pid=2342))
LISTEN  0       128     127.0.0.1:5432      0.0.0.0:*          users:(("postgres",pid=843))

Flags: -t for TCP, -l for listening, -n for numeric ports (skip DNS lookups), -p for process name (requires root).

If a deploy fails with EADDRINUSE, this is the command to identify what's holding the port:

sudo ss -tlnp | grep :3000

The older netstat -tlnp does the same thing but is deprecated on most modern distros — ss is the right default in 2026.

For active (not listening) connections — useful when debugging why is the database CPU pinned:

sudo ss -tn state established

Logs — where they live and how to read them

On any systemd-based distro (every modern Linux distribution covered in our distro guide), system logs live in the journal, queried with journalctl:

sudo journalctl -u nginx                      # all logs for the nginx service
sudo journalctl -u nginx -f                   # follow (tail -f equivalent)
sudo journalctl -u nginx --since "10 min ago"
sudo journalctl -u nginx -p err               # error level and above only
sudo journalctl -u nginx -n 100               # last 100 lines
sudo journalctl --since today                 # everything since midnight
sudo journalctl --since "2026-05-23 14:00"    # specific timestamp

Application logs that don't go through systemd still typically land in /var/log/:

tail -n 100 /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

For application-specific log directories (WordPress, Rails, Laravel, Node), check /var/log/<appname>/ or the application's working directory. A useful one-liner to find log files modified in the last day:

sudo find /var/log -type f -mtime -1 -name "*.log"

To search across many log files for an error pattern:

sudo grep -r --include="*.log" "FATAL" /var/log/ | tail -20

File system — finding and inspecting files

find is the workhorse:

sudo find /var/www -name "*.php" -mtime -7    # PHP files modified in the last 7 days
sudo find / -name "config.yml" 2>/dev/null    # locate a config across the filesystem
sudo find /tmp -type f -mtime +30 -delete     # delete files older than 30 days in /tmp

-mtime is in days; positive numbers mean older than, negative numbers mean newer than. find ... -delete is destructive — always run without -delete first to see what would be affected.

To inspect file permissions and ownership:

ls -la /var/www/
stat /var/www/index.php

stat is the more detailed view — it shows owner, group, mode in both numeric and symbolic form, plus access/modify/change timestamps.

To find what's holding a file open (useful when an unmount fails or a log file can't be rotated):

sudo lsof | grep "/var/log/nginx/access.log"
sudo lsof -p 1810                              # everything open by PID 1810
sudo lsof -i :3000                             # what has port 3000 open

A pre-deploy health-check script

Put the commands together as a sanity check that runs before any deploy that matters:

#!/usr/bin/env bash
# Pre-deploy health check — fail the deploy if any of these thresholds are breached.
set -euo pipefail

# 1. Disk space — abort if any mounted filesystem is over 90% full
high_disk=$(df -h --output=pcent,target | tail -n +2 | awk '{gsub("%",""); if ($1 > 90) print $0}')
if [ -n "$high_disk" ]; then
  echo "Disk space warning:"
  echo "$high_disk"
  exit 1
fi

# 2. Memory — abort if available memory under 200 MB
avail_mb=$(free -m | awk '/^Mem:/{print $7}')
if [ "$avail_mb" -lt 200 ]; then
  echo "Memory critical: ${avail_mb}MB available"
  exit 1
fi

# 3. Confirm the application port is currently free
if sudo ss -tln | grep -q ":3000 "; then
  echo "Port 3000 already in use:"
  sudo ss -tlnp | grep :3000
  exit 1
fi

# 4. Confirm the previous release directory exists and is readable
test -d /var/www/current || { echo "No /var/www/current symlink"; exit 1; }

# 5. Check for recent errors in the application log (last 5 min)
err_count=$(sudo journalctl -u myapp --since "5 min ago" -p err --no-pager | wc -l)
if [ "$err_count" -gt 10 ]; then
  echo "App has ${err_count} errors in the last 5 minutes — investigate before deploying"
  exit 1
fi

echo "All pre-deploy checks passed."

Wire this as a pre-deploy SSH command in your deploy tool of choice. Anything that fails the script aborts the deploy before any code changes — so a stuck server, a half-mounted disk, or a runaway process can't be silently buried under a fresh release.

For automating these checks across a fleet of servers, DeployHQ's build pipelines and SSH-based deploys let you run the health-check script as a pre-deploy hook — the deploy aborts cleanly before changing anything if the script exits non-zero. Start a free trial to wire health checks into a Git-driven deploy pipeline.


Need help? Email support@deployhq.com or follow @deployhq on X.