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:#fb8c00What’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:#43a047Swap 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:
- Reclaim page cache — memory used to store recently read files can be freed because it can be re-read from disk
- Reclaim anonymous pages to swap — pages without a backing file (application heap, stack) are moved to swap
- 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:#e53935The 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 downtimeThe 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 downtimeThe 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.
| Dimension | Performance Tuning | System Survivability |
|---|---|---|
| Goal | Optimal under normal conditions | Stay alive under abnormal conditions |
| Time perspective | Every request, every millisecond | Hours, days, weeks of uptime |
| Main metrics | Latency, throughput | Availability, MTTR |
| Swap’s role | Must not be used routinely | Used during emergencies |
| Who cares | Performance engineers | SREs, 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:#43a047A 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=1makes 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_adjprotection 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.