The No-Swap Server Myth: When So-Called “Best Practice” Kills the System
15 min read

The No-Swap Server Myth: When So-Called “Best Practice” Kills the System

There’s a sentence circulating in the DevOps community like a dogma that no longer needs questioning: “best practice for servers is no swap.” This sentence is spoken with full confidence, agreed to with nods, and then implemented on production servers without much questioning. The problem: in many cases, this understanding isn’t just wrong — it’s actively dangerous. A server configured without swap based on this dogma becomes a server that dies more easily, dies more brutally, and is harder to debug after that death. This article dissects where this myth comes from, why it’s wrong for most cases, how the OOM Killer works, and what should actually be done.

Where This Myth Comes From

Understanding the origin of a misconception is the first step to properly debunking it. The “no-swap server” myth wasn’t born from a vacuum — it was born from a valid context, then generalized to irrelevant situations.

The Context That Gave Birth to This Claim

There are three main sources from which this narrative spreads.

First: systems needing very low latency. Swap is indeed dangerous for high-frequency trading systems, real-time audio processing, or other applications intolerant to latency jitter. When a process is swapped out to disk then called back, there’s a delay that can reach hundreds of milliseconds to several seconds — unacceptable for such use cases. In this context, avoiding swap is the right and reasoned decision.

Second: bad experiences with Linux default swap configuration. Linux’s default vm.swappiness is 60, which means the kernel is quite aggressive in moving memory pages to swap even when RAM is still sufficiently available. This creates a server experience that feels “hung” — responsive but heavy — because of context switching between RAM and disk happening too often. Engineers experiencing this tend to conclude that swap is the problem, when the actual problem is an unadjusted swappiness configuration.

Third: generalization of narratives from the container and cloud ecosystem. Kubernetes indeed officially doesn’t recommend swap because it disturbs its resource allocation calculations. Some cloud providers also don’t provide swap by default. From these two facts, many engineers conclude that “no swap” is the industry standard — even though the context is very specific.

flowchart TD
    A[Experience with badly<br/>configured swap] --> D[Generalization:<br/>'Swap is bad']
    B[Narrative from the<br/>Kubernetes and container ecosystem] --> D
    C[Latency-sensitive systems<br/>that indeed don't need swap] --> D
    D --> E[Dogma:<br/>'Servers must not have swap']
    E --> F[Production server<br/>with no swap at all]
    F --> G{Memory exhausted?}
    G -- Yes --> H[OOM Killer activates<br/>Process SIGKILLed]
    G -- No --> I[Safe]
    H --> J[Downtime without<br/>a mitigation chance]

    style H fill:#ffebee,stroke:#e53935
    style J fill:#ffebee,stroke:#e53935
    style E fill:#fff3e0,stroke:#fb8c00

What’s missing from this generalization is an understanding of what swap actually does — and what happens when swap doesn’t exist in the right situation.


How the Linux Kernel Manages Memory

Before discussing swap further, there needs to be an accurate mental model of how Linux manages memory. This isn’t just theory — this understanding directly determines why swap can save a system or destroy it.

The Memory Hierarchy in Linux

Linux manages memory in several layers. When a process needs a memory page, the kernel searches from the fastest layer to the slowest.

flowchart TD
    P[Process needs a memory page]
    P --> A{In CPU cache?}
    A -- Yes --> Z1[Direct access<br/>nanoseconds]
    A -- No --> B{In RAM?}
    B -- Yes --> Z2[Take from RAM<br/>microseconds]
    B -- No --> C{In page cache<br/>of a recently read file?}
    C -- Yes --> Z3[Take from page cache<br/>microseconds]
    C -- No --> D{Is there swap?}
    D -- Yes --> Z4[Swap in from disk<br/>milliseconds - seconds]
    D -- No --> E[No options left]
    E --> F[OOM Killer activates<br/>Process SIGKILLed]

    style F fill:#ffebee,stroke:#e53935
    style Z4 fill:#fff3e0,stroke:#fb8c00
    style Z1 fill:#e8f5e9,stroke:#43a047
    style Z2 fill:#e8f5e9,stroke:#43a047

Swap sits at the last layer before total failure. It’s slow — nobody disputes that. But “slow but alive” is almost always better than “fast but dead” in a production server context.

What the Kernel Decides When RAM Is Full

When RAM approaches its limit, the kernel has several strategies it tries in sequence:

  1. Reclaim page cache — memory used to store recently read files can be freed because it can be re-read from disk
  2. Reclaim anonymous pages to swap — pages without a backing file (application heap, stack) are moved to swap
  3. OOM Killer — if there’s no swap and page cache is exhausted, the kernel has no other choice

Swap is the only option allowing the kernel to save anonymous pages (whose contents can’t simply be discarded because no file on disk stores them) without having to kill the process.

“Anonymous pages” are memory pages allocated by an application for runtime data — variables, heap, stack. Unlike “file-backed pages” whose contents can be restored by reading a file from disk. Anonymous pages can only be saved through swap — there’s no other way.

The OOM Killer: Understanding the Often Misunderstood Mechanism

The OOM Killer (Out-of-Memory Killer) is a Linux kernel mechanism activated when the system runs out of memory and there’s no other way to free it. It selects one or more processes to kill using SIGKILL — a signal that can’t be caught or ignored by applications.

How the OOM Killer Chooses Its Victim

The kernel assigns a “badness” score to every process based on several factors:

flowchart TD
    OOM["OOM Killer activates"] --> SCAN["Scan all processes"]
    SCAN --> SCORE["Compute each process's oom_score"]

    SCORE --> F1["+ Memory usage<br/>more usage = higher score"]
    SCORE --> F2["+ Child process memory"]
    SCORE --> F3["- Long-running processes<br/>longer = lower score"]
    SCORE --> F4["- Kernel processes and important daemons"]
    SCORE --> ADJ["oom_score_adj<br/>can be set manually by the application"]

    F1 --> SELECT["Select the process with<br/>the highest score"]
    F2 --> SELECT
    F3 --> SELECT
    F4 --> SELECT
    ADJ --> SELECT

    SELECT --> KILL["SIGKILL - cannot be caught"]
    KILL --> LOG["Write to the kernel log<br/>Out of memory: Kill process X"]

    style KILL fill:#ffebee,stroke:#e53935
    style OOM fill:#ffebee,stroke:#e53935

The process consuming the most memory gets the highest score and is most likely to be killed. But this isn’t a guarantee — the kernel has complex heuristics, and the result can be surprising. There are cases where the OOM Killer kills the “wrong” process from an operator’s perspective: not the memory-leaking application, but a database or web server that happens to have a large memory footprint.

SIGKILL’s Characteristics That Make the OOM Killer Brutal

Unlike SIGTERM, which applications can catch to do a graceful shutdown, SIGKILL can’t be caught, blocked, or ignored by a process. The kernel immediately stops the process without giving it a chance to:

  • Close database connections cleanly
  • Finish transactions in progress
  • Flush buffers to disk
  • Send error signals to connected clients
  • Clean up allocated resources

The result can be worse than just a dead process: half-finished database transactions can leave inconsistent data, connections not closed cleanly can leave the client-side connection pool exhausted, and files being written can become corrupted.

Reading OOM Killer Logs

Recognizing OOM Killer logs is an important skill for every engineer managing Linux servers:

# Search for OOM logs in the system log
sudo dmesg | grep -i "out of memory"
sudo journalctl -k | grep -i "oom"

# Output you'll see:
# kernel: Out of memory: Kill process 12345 (php-fpm) score 892 or sacrifice child
# kernel: Killed process 12345 (php-fpm) total-vm:3145728kB, anon-rss:2097152kB
# kernel: oom_kill_process: OOM victim 12345 (php-fpm) is in memcg /system.slice

# Check the oom_score of running processes (0-1000, higher is more at risk)
cat /proc/$(pgrep php-fpm | head -1)/oom_score

# Set oom_score_adj to protect important processes from the OOM Killer
# A value of -1000 means the process will never be killed by the OOM Killer
echo -1000 > /proc/$(pgrep mysql | head -1)/oom_score_adj

Case Study: PHP-FPM Without Swap

This is a very common scenario and very illustrative of the problem that occurs when the “no swap” dogma is applied without understanding context.

Initial Condition

Server specs:
  RAM       : 4 GB
  Swap      : none (following the "best practice")
  OS        : Ubuntu 22.04
  Workload  : PHP-FPM + Nginx + MySQL (all on one server)

PHP-FPM configuration:
  pm                  = dynamic
  pm.max_children     = 20
  pm.memory_limit     = 256M per worker
  
Estimated maximum memory usage:
  PHP-FPM  : 20 workers × 256MB = 5.12 GB  ← exceeds RAM!
  MySQL    : ~800 MB
  OS + misc: ~500 MB
  Total    : ~6.4 GB vs 4 GB of available RAM

This configuration was already problematic from the start — pm.max_children is too large for the available RAM. But this is a very common mistake, and swap can be the difference between “the system slows down and gives time for mitigation” vs “the system dies immediately”.

The Failure Timeline Without Swap

sequenceDiagram
    participant Traffic as Traffic Spike
    participant PHP as PHP-FPM Workers
    participant Kernel as Linux Kernel
    participant OOM as OOM Killer
    participant DB as MySQL

    Traffic->>PHP: Requests increase 3x from normal
    PHP->>Kernel: Spawn new worker, request memory
    Kernel-->>PHP: Allocation OK (RAM still available)
    PHP->>Kernel: Spawn another worker, request more memory
    Kernel-->>PHP: Allocation OK (RAM starting to fill)
    PHP->>Kernel: Request more memory
    Note over Kernel: RAM almost exhausted<br/>No swap<br/>No reclaimable page cache
    Kernel->>OOM: Activate the OOM Killer
    OOM->>PHP: SIGKILL — the PHP-FPM process with the highest score
    Note over PHP: Dies without graceful shutdown<br/>Client connections suddenly cut
    OOM->>DB: SIGKILL — MySQL also has a large footprint
    Note over DB: Half-finished transactions stop<br/>Potential data corruption
    Note over Traffic: All requests fail<br/>Total downtime

The Different Timeline With Swap

sequenceDiagram
    participant Traffic as Traffic Spike
    participant PHP as PHP-FPM Workers
    participant Kernel as Linux Kernel
    participant Swap as Swap (2 GB)
    participant Ops as Operator

    Traffic->>PHP: Requests increase 3x from normal
    PHP->>Kernel: Spawn new worker, request memory
    Kernel-->>PHP: Allocation OK from RAM
    PHP->>Kernel: Request more memory, RAM almost full
    Kernel->>Swap: Move inactive anonymous pages to swap
    Swap-->>Kernel: Space available
    Kernel-->>PHP: Allocation OK (from a RAM + swap combination)
    Note over PHP: System slows down but doesn't die
    Note over Ops: Monitoring alert triggered:<br/>"Swap usage 40%, server slow"
    Ops->>PHP: Investigate — find the worker configuration is too large
    Ops->>PHP: Reduce pm.max_children, restart PHP-FPM
    Note over PHP,Swap: System returns to normal<br/>Users experience high latency<br/>but not total downtime

The difference couldn’t be clearer: without swap, the incident ends in total downtime and potential data corruption. With swap, the incident ends in performance degradation that can still be mitigated.


The Difference Between Performance Tuning and System Survivability

This is the concept most often confused in discussions about swap, and understanding it correctly is the key to making the right decision.

Performance tuning is optimization for normal cases — how the system behaves when the load is within planned parameters. Here, swap indeed shouldn’t be used routinely because it adds latency.

System survivability is the system’s ability to stay alive and recoverable when conditions go outside the norm — unexpected traffic spikes, undetected memory leaks, or batch jobs consuming more memory than estimated. Here, swap is a very valuable safety net.

DimensionPerformance TuningSystem Survivability
GoalOptimal under normal conditionsStay alive under abnormal conditions
Time perspectiveEvery request, every millisecondHours, days, weeks of uptime
Main metricsLatency, throughputAvailability, MTTR
Swap’s roleMust not be used routinelyUsed during emergencies
Who caresPerformance engineersSREs, operators, on-call

A correctly configured swap doesn’t affect performance tuning at all — because with low swappiness, swap is almost never touched under normal conditions. But when abnormal conditions occur, swap is the difference between “system alive but slow” and “system completely dead”.

“Swap being used means something is wrong” is a statement that’s true but dangerous if misinterpreted. What’s true: swap used routinely and continuously means something is wrong — a memory leak, bad configuration, or undersizing. But swap used occasionally during spikes means it’s working exactly as designed.

Correct Swap Configuration for Production Servers

This isn’t about “whether to use swap”, but about “how to configure swap so it works like an airbag — present but never used except when truly needed”.

The Right Swap Size

Swap isn’t a RAM extension — it’s an emergency buffer. Its size doesn’t need to be large.

# Swap size recommendations based on RAM:
# RAM ≤ 2 GB  → swap 1-2 GB
# RAM 2-8 GB  → swap 2 GB
# RAM 8-16 GB → swap 2-4 GB
# RAM > 16 GB → swap 4 GB is enough (the goal is survivability, not RAM extension)

# Create a swap file (more flexible than a swap partition)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# Verify
sudo swapon --show
free -h

# Make it permanent by adding to /etc/fstab
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Setting vm.swappiness

This is the most important parameter. The default value of 60 is too aggressive for production servers.

# Check the current swappiness value
cat /proc/sys/vm/swappiness

# Set a low swappiness — swap is only used when truly critical
# Value 1: swap is only used when there's no other option
# Value 10: swap starts being used when ~10% of RAM remains
sudo sysctl vm.swappiness=1

# Make it permanent
echo 'vm.swappiness=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# ANTI-PATTERN: using the default swappiness or a high value
# vm.swappiness=60  ← swap used too aggressively, causing poor performance
# vm.swappiness=100 ← swap and RAM treated equally, very bad for servers

Setting vm.vfs_cache_pressure

This parameter controls how aggressively the kernel reclaims memory used for page cache (caching file contents from disk). The right value helps a server maintain I/O performance.

# Default is 100 — the kernel aggressively reclaims page cache
# A lower value means the kernel retains page cache more
# For web/PHP servers that read many files: set to 50
sudo sysctl vm.vfs_cache_pressure=50

# Make it permanent
echo 'vm.vfs_cache_pressure=50' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Recommended combination for a typical production server:
# vm.swappiness=1
# vm.vfs_cache_pressure=50

Protecting Critical Processes from the OOM Killer

When the OOM Killer activates, you need to make sure the most important processes don’t become the first victims.

# Check a process's oom_score_adj (range: -1000 to 1000)
# -1000 = will never be killed by the OOM Killer
# 0 = default, uses the kernel heuristic
# 1000 = always the first candidate

# Protect the database process from the OOM Killer
# (run at startup, not when the system is already critical)
PID_MYSQL=$(pgrep -x mysqld | head -1)
echo -500 > /proc/${PID_MYSQL}/oom_score_adj

# Or set via a systemd service for permanent protection
# In /etc/systemd/system/mysql.service.d/override.conf:
# [Service]
# OOMScoreAdjust=-500

# Make PHP-FPM processes easier to kill than the database
# (better PHP dies than data gets corrupted)
PID_PHP=$(pgrep -x php-fpm | head -1)
echo 200 > /proc/${PID_PHP}/oom_score_adj

Monitoring That Must Exist

Unmonitored swap provides no added value — you won’t know when the system approaches its limits or when something is wrong.

# Simple monitoring script — add to cron or a monitoring system
#!/bin/bash

# Check swap usage
SWAP_USED=$(free | awk '/^Swap:/{print $3}')
SWAP_TOTAL=$(free | awk '/^Swap:/{print $2}')

if [ "$SWAP_TOTAL" -gt 0 ]; then
    SWAP_PCT=$((SWAP_USED * 100 / SWAP_TOTAL))
    if [ "$SWAP_PCT" -gt 20 ]; then
        echo "WARNING: Swap usage ${SWAP_PCT}% — investigate memory usage"
        # Send an alert to Slack, PagerDuty, or a monitoring tool
    fi
fi

# Check whether the OOM Killer activated in the last hour
OOM_COUNT=$(dmesg --since "1 hour ago" | grep -c "Out of memory" 2>/dev/null || \
            journalctl -k --since "1 hour ago" | grep -c "Out of memory" 2>/dev/null)

if [ "$OOM_COUNT" -gt 0 ]; then
    echo "CRITICAL: OOM Killer activated ${OOM_COUNT}x in the last hour"
    dmesg --since "1 hour ago" | grep "Out of memory"
fi

# Show the top 5 processes by oom_score
echo "=== Top 5 processes with the highest oom_score ==="
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
    score=$(cat /proc/$pid/oom_score 2>/dev/null || echo 0)
    comm=$(cat /proc/$pid/comm 2>/dev/null || echo "unknown")
    echo "$score $comm (PID: $pid)"
done | sort -rn | head -5

Why Swap Also Improves RAM Efficiency

There’s one swap benefit often overlooked in this discussion: with swap present, the kernel can be more aggressive in moving inactive memory pages to swap — freeing physical RAM to be used as page cache.

Page cache is one of the most important performance optimizations in Linux. When the same file is read repeatedly (application configs, shared libraries, templates), Linux stores it in RAM as cache. Subsequent accesses to that file become very fast because there’s no need to read the disk.

flowchart LR
    subgraph NoSwap["Server Without Swap"]
        RAM_A["RAM 4GB<br/>─────────────<br/>Active apps: 3.5GB<br/>Page cache: 0.5GB<br/>(limited because of OOM fear)"]
    end

    subgraph WithSwap["Server With Swap (2GB)"]
        RAM_B["RAM 4GB<br/>─────────────<br/>Active apps: 2.5GB<br/>Page cache: 1.5GB<br/>(more because<br/>passive pages are swapped)"]
        SWAP_B["Swap 2GB<br/>─────────────<br/>Passive pages: 1GB<br/>(rarely accessed<br/>heap data)"]
    end

    RAM_B <--> SWAP_B

    style NoSwap fill:#fff3e0,stroke:#fb8c00
    style WithSwap fill:#e8f5e9,stroke:#43a047

A server with correctly configured swap can have a larger page cache — which means faster I/O, not slower. This is a counterintuitive but very real benefit.


When No Swap Is Indeed Right

This article isn’t an argument that swap must always exist. There are situations where having no swap is the correct and reasoned decision.

NO SWAP IS RIGHT if all these conditions are met:
  ✓ Latency-sensitive systems with strict SLAs (trading, real-time)
  ✓ Better a process dies than experience high latency
  ✓ Excellent monitoring and alerting exist before OOM
  ✓ Strict resource limits exist at the application level
  ✓ A tested recovery strategy exists (auto-restart, failover)
  ✓ The team understands and is ready for the OOM Killer consequences

NO SWAP IS A BAD DECISION if:
  ✗ The only reason is "it's best practice" without understanding context
  ✗ There's no adequate memory monitoring
  ✗ The workload fluctuates and is hard to predict
  ✗ There's no tested recovery strategy
  ✗ The server runs a database or stateful systems
  ✗ The team doesn't understand how to read OOM Killer logs

SPECIFICALLY — Kubernetes clusters:
  ✗ Kubernetes officially doesn't support swap on nodes
     because it disturbs resource accounting and scheduling
  ✓ Use proper resource requests/limits per pod instead
  ✓ Use the Vertical Pod Autoscaler for automatic adjustments

Summary

  • “Servers must not have swap” isn’t a universal best practice — it’s a correct conclusion for a narrow context (latency-sensitive systems) incorrectly generalized to all situations.
  • Swap is designed for survivability, not performance — it’s not a way to extend RAM, but an airbag that saves the system when abnormal conditions occur.
  • The OOM Killer is more brutal than swap — SIGKILL can’t be caught by applications, can kill the wrong process, and can leave data in an inconsistent state. Swap provides time for observation and mitigation.
  • vm.swappiness=1 makes swap almost never used — with this value, swap only activates when conditions are truly critical, not affecting performance under normal conditions.
  • Constantly active swap is an alarm, not a configuration success — if swap usage keeps rising, that’s a signal of a memory leak or wrong configuration, not proof that swap is useful.
  • Swap can improve page cache efficiency — by moving passive pages to swap, physical RAM can be used more for page cache, improving I/O performance.
  • oom_score_adj protection for critical processes — database processes must be protected so they don’t become the OOM Killer’s first victim; processes that can be more easily restarted are better candidates.
  • Kubernetes is a valid exception — swap indeed doesn’t fit Kubernetes nodes because it disturbs resource accounting; use per-pod resource limits instead.

Portfolio