Add Swap to a Small Django Server

Add a swap file to a 1-2 GB Django server so pip installs, migrations, and traffic spikes do not trigger the OOM killer: sizing, fstab, swappiness, Docker and cloud caveats, and when swap is the wrong fix.

Problem statement

Small VPS plans with 1 or 2 GB of RAM run a Django app with Gunicorn, Nginx, and sometimes PostgreSQL and Redis on the same machine. Normal traffic fits, but occasional peaks do not: pip install compiling a package, collectstatic with heavy post-processing, a Celery task loading a large file, or a few extra concurrent requests. When memory runs out, the kernel's OOM killer terminates a process, often PostgreSQL or a Gunicorn worker, and the site fails.

Many cloud images ship without swap. A modest swap file gives the kernel somewhere to move rarely used memory pages during short peaks, turning a crash into a brief slowdown. This guide adds one correctly and explains when more memory, not swap, is the real fix.

Memory pressure on a small VPSNormal loadfits in RAMShort peakdeploy or burstSwap fileholds idle pagesNo OOM killbrief slowdownSwap absorbs short peaks; steady swapping means you need more RAM or fewer workers.
  1. Check current memory and swap: free -h and swapon --show.
  2. Create a 1-2 GB swap file with fallocate, set permissions to 600, run mkswap, and swapon.
  3. Add it to /etc/fstab so it survives reboots.
  4. Set vm.swappiness to a low value (for example 10) so swap is used for idle pages, not active ones.
  5. Monitor swap usage: occasional use is fine, sustained swapping means resize the server or reduce workers.
  6. Do not rely on swap to run more Gunicorn workers than RAM can hold.

Step-by-step solution

1. Check the current state

free -h
swapon --show
cat /proc/sys/vm/swappiness

If swapon --show prints nothing, there is no swap. Also check for recent OOM kills, which confirm the need:

sudo journalctl -k | grep -i -E "out of memory|killed process" | tail

2. Create the swap file

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --show

Sizing for small Django servers: 1 GB of swap for a 1 GB server, 1-2 GB for a 2 GB server. More swap rarely helps: if the system needs several gigabytes of swap, it is thrashing and responses will be extremely slow anyway.

On filesystems where fallocate does not produce a usable swap file (some older XFS or Btrfs setups), use dd instead:

sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress

Btrfs needs additional steps (a dedicated subvolume or btrfs filesystem mkswapfile on recent versions).

3. Make it permanent

echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab
sudo findmnt --verify

Without the fstab entry, swap disappears on the next reboot, often unnoticed until the next OOM kill.

4. Tune swappiness

echo "vm.swappiness = 10" | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system
cat /proc/sys/vm/swappiness

A low value tells the kernel to prefer dropping file cache over swapping application memory, so swap holds pages that are genuinely idle (a Celery process that has not run a task in hours, parts of PostgreSQL's memory that are rarely touched), while active Gunicorn workers stay in RAM.

5. Fit the app to the RAM you have

Swap buys headroom; it does not create capacity. Keep the steady state within physical memory:

  • Measure per-worker memory: ps -o pid,rss,cmd -C gunicorn. Multiply by the worker count and add PostgreSQL, Redis, Celery, and Nginx.
  • On a 1 GB server, two or three Gunicorn workers are typically the limit with PostgreSQL on the same machine.
  • Use --max-requests with jitter to recycle workers that grow over time.
  • Limit Celery concurrency; each prefork child is a full process.
  • Tune PostgreSQL's shared_buffers and work_mem down to match a small machine.
  • Build Docker images in CI instead of on the server; builds are among the largest memory spikes.
where 2 GB goes on a typical small serverOS and page cache floor250 MBGunicorn (3 workers)540 MBCelery (2 children)400 MBPostgreSQL400 MBRedis + Nginx90 MBTotal steady state1,680 MB of 2,0480 MB1024 MB2048 MBA deploy that runs pip install on top of this is what pushes it overdjango-deployment.com
Illustrative numbers: measure your own workers with ps before sizing, then keep the total below physical RAM.

6. Monitor swap

vmstat 5 5

The si and so columns show pages swapped in and out per second. Occasional non-zero values during deploys are fine. Continuous swapping during normal traffic means the server is under-provisioned: resize, reduce workers, or move PostgreSQL to a separate server.

reading vmstat: headroom or a sizing problem?Healthy: swap as headroom✓si/so near zero in normal traffic✓brief spikes during deploys and builds✓swap holds idle pages (a quiet Celerychild)Thrashing: swap as capacity✕si/so non-zero every sample under load✕response times climb, CPU waits on I/Ofix: fewer workers or a bigger serverSustained swapping is a sizing signal, not a tuning problemdjango-deployment.com
Watch the si and so columns: occasional movement is fine, constant movement means the app does not fit.

Add alerts for swap usage above 50 percent and for any OOM kill.

Verification check:

free -h
swapon --show
grep swapfile /etc/fstab
cat /proc/sys/vm/swappiness

Reboot once during a maintenance window and confirm swap is active again.

Explanation

Linux can move memory pages that have not been used recently to swap, freeing RAM for active processes. Without swap, when memory is exhausted, the only options are dropping file cache and, once that is gone, killing a process. For small Django servers, where memory peaks are short and predictable (deploys, periodic tasks), a small swap area with low swappiness prevents those kills while keeping active processes in RAM. Swap is slow compared with RAM, which is why it is a safety net, not a way to run more than the machine can hold.

Edge cases / notes

Containers and memory limits

Docker containers can use host swap unless limited. A container memory limit without a swap limit (memory-swap equal to memory) means the container is killed at the limit regardless of host swap. Decide deliberately per service.

Kubernetes nodes

Kubernetes traditionally requires swap to be disabled on nodes; newer versions support swap under specific configurations. Follow your cluster's guidance rather than adding swap to nodes directly.

Cloud burst credits and disks

On instances with network-attached disks, heavy swapping also consumes disk I/O, which may be limited or billed. Another reason to treat sustained swapping as a sizing problem.

When to automate this

Add swap creation to your provisioning (cloud-init or Ansible) for small instance types, and ship the swappiness setting with it. Include swap usage and OOM kill alerts in your standard monitoring.

Next steps

FAQ

Should a production Django server have swap?

On small servers, a modest swap file is a sensible safety net against short memory peaks. It should not be used continuously.

How much swap does a Django server need?

About 1-2 GB for servers with 1-2 GB of RAM. Larger swap mostly lets a thrashing server stay slow for longer.

What swappiness value should I use?

A low value such as 10 keeps active processes in RAM and uses swap mainly for idle pages.

Will swap let me run more Gunicorn workers?

Not usefully. Workers that are swapped out respond very slowly. Size workers to fit in physical RAM.

Keep going