---
title: "Deploy Nginx"
description: "High-performance web server, reverse proxy and HTTP cache"
category: "Other"
url: https://railway.com/deploy/nginx
---

# Deploy Nginx

High-performance web server, reverse proxy and HTTP cache

**[Deploy Nginx on Railway](https://railway.com/template/nginx)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/nginx/manifest.json

- **Creator:** A3A
- **Category:** Other

## Template content

### nginx https://cdn.simpleicons.org/nginx/009639.svg

- **Source:** https://github.com/gridalpha/nginx-railway
- **Health check:** /healthz
- **Public domain:** Yes

## Documentation

# Deploy and Host NGINX on Railway

NGINX is the web server behind roughly a third of the internet's busiest sites. It serves static files, forwards HTTP, load-balances across backends and caches what it proxies, from one small event-driven process that handles tens of thousands of concurrent connections on modest hardware. Teams reach for it when they need a front door: to publish a built site, put several backends behind one domain, or stand in front of an application never meant to face the internet.

This template deploys a single `nginx` service with a persistent volume, with the awkward parts of running a web server on a platform already handled. Deploy NGINX here and it listens on the port Railway assigns over IPv4 and IPv6, sizes its worker processes from the container's CPU quota rather than the host's, and recovers the true client address from `X-Forwarded-For` instead of logging Railway's rotating edge. The volume mounts at `/data`: your site lives in `/data/www` and the cache in `/data/cache`, so both survive every redeploy. The configuration is rendered from environment variables at boot and checked with `nginx -t` before the server starts.

![Diagram of the single NGINX service and its volume on Railway](https://res.cloudinary.com/rroe4rtk/image/upload/v1788764846/nginx-architecture.png)

## Getting Started with NGINX on Railway

Open the deployed URL as soon as the build finishes. There is no login and no setup wizard — NGINX serves a placeholder page describing the available settings, which is also how you confirm the deployment is healthy. It is seeded onto the volume only while the site root is empty, so it disappears the moment you publish your own `index.html`.

The fastest way to publish a real site is to set `SITE_SOURCE_URL` to a `.tar.gz` or `.zip` of your build output. It is fetched on every boot and unpacked only when its contents have changed, so redeploying publishes a new build. Alternatively, upload files onto the volume with `railway volume files --volume  upload ./index.html /www/index.html`. For a single-page app, set `SPA_MODE=true` so unknown paths fall back to `index.html`.

To put NGINX in front of your other Railway services, set `PROXY_ROUTES` to a comma-separated list of `/path=upstream` pairs — for example `/api=http://api.railway.internal:3000`. Following `proxy_pass`'s own convention, an upstream ending in `/` strips the matched prefix and one without it passes the path through. Check `/healthz`, which always answers `200` anonymously and is the path Railway's health check uses.

![NGINX landing page listing static hosting and proxy settings](https://res.cloudinary.com/rroe4rtk/image/upload/v1788764849/nginx-landing.png)

![A custom static site served by NGINX from the Railway volume](https://res.cloudinary.com/rroe4rtk/image/upload/v1788764850/nginx-static-site.png)

![Live NGINX checks showing proxy, cache and header results](https://res.cloudinary.com/rroe4rtk/image/upload/v1788764851/nginx-gateway-checks.png)

## About Hosting NGINX

NGINX solves two problems that appear in almost every deployment. The first is serving files quickly and correctly — right MIME types, compression, cache headers and a real 404. The second is routing: the internet reaches one hostname, and something must decide which backend answers which path. Self-hosting puts that logic in configuration you control.

Key features in this deployment:

- Static file serving from a persistent volume, with optional SPA fallback and directory listings
- Path-based reverse proxying to any HTTP upstream, including private Railway services
- On-disk response caching for proxied traffic, with an `X-Cache-Status` header
- gzip compression, plus `gzip_static` so a pre-built `.gz` sibling is served as-is
- HTTP basic auth over the whole site, hashed at boot, and per-client-IP rate limiting
- Security headers — `nosniff`, `X-Frame-Options`, referrer policy, COOP and HSTS — on every response

There is one service and one volume. NGINX needs no database, no queue and no worker tier.

## Why Deploy NGINX on Railway

Railway removes the parts of running a web server that are not about serving pages:

- TLS certificates issued and renewed at the edge, with HTTP redirected to HTTPS
- A persistent volume for site content and cache, surviving every deploy
- Private networking to your other services, so backends need no public domain
- Health checks, restart policy and logs configured out of the box
- Custom domains attached without touching a config file

## Common Use Cases

- Publishing a static site, docs build or SPA, with compression and cache headers already set
- Putting one public domain in front of several private Railway services, split by URL path
- Caching an expensive or rate-limited upstream API on disk to cut origin traffic
- Fronting an app that has no auth of its own with basic auth and per-IP rate limiting

## Dependencies for NGINX

- **`nginx`** — the whole deployment. Built from `nginx:alpine` (mainline, currently 1.31.5) plus a boot script that renders the configuration. Source: [gridalpha/nginx-railway](https://github.com/gridalpha/nginx-railway).
- **Volume** — mounted at `/data`. The site root and cache sit one level below the mount root, because every Railway volume ships a `lost+found` directory.

### Environment Variables Reference

| Variable | Default | Purpose |
|---|---|---|
| `SITE_ROOT` | `/data/www` | Directory served as the site root |
| `SITE_SOURCE_URL` | *(unset)* | Archive unpacked over the site root at boot |
| `SITE_SOURCE_STRIP` | `0` | `--strip-components`; use `1` for a GitHub source tarball |
| `SPA_MODE` | `false` | Fall back to `/index.html` for unknown paths |
| `DIRECTORY_LISTING` | `false` | Serve an index for directories with no index file |
| `PROXY_ROUTES` | *(unset)* | Comma-separated `/path=upstream` proxy routes |
| `CACHE_ENABLED` | `false` | Cache proxied responses on the volume |
| `GZIP_ENABLED` | `true` | gzip plus `gzip_static` |
| `HSTS_MAX_AGE` | `31536000` | `0` removes the HSTS header |
| `BASIC_AUTH_USER` | *(unset)* | Set with `BASIC_AUTH_PASSWORD` to require a login |
| `RATE_LIMIT_RPS` | *(unset)* | Requests per second per client IP |

### Deployment Dependencies

- Base image: [`nginx:alpine`](https://hub.docker.com/_/nginx) on Docker Hub
- Source repository: [gridalpha/nginx-railway](https://github.com/gridalpha/nginx-railway)
- Upstream project: [nginx/nginx](https://github.com/nginx/nginx) and the [official documentation](https://nginx.org/en/docs/)

## Hardware Requirements for Self-Hosting NGINX

NGINX is unusually cheap to run. The defaults below comfortably serve a static site or a small gateway.

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 0.1 vCPU | 1 vCPU |
| RAM | 64 MB | 512 MB |
| Storage | 1 GB volume | 5 GB volume, more if caching |
| Runtime | nginx 1.31 (Alpine) | nginx 1.31 (Alpine) |

Raise storage first: the volume holds both your site and the cache, so keep `CACHE_MAX_SIZE` below the volume size.

## Self-Hosting NGINX

The image runs anywhere Docker does. This clones the source repository and builds it:

```
git clone https://github.com/gridalpha/nginx-railway.git
cd nginx-railway
docker build -t nginx-railway .
docker run -p 8080:8080 -e PORT=8080 -v nginx-data:/data nginx-railway
```

The next block runs the same server as a gateway: a site from the volume, two paths proxied to backends, and the responses cached.

```
docker run -p 8080:8080 \
  -e PORT=8080 \
  -e SPA_MODE=true \
  -e CACHE_ENABLED=true \
  -e PROXY_ROUTES=/api=http://api:3000,/auth=http://auth:9000/ \
  -e BASIC_AUTH_USER=admin -e BASIC_AUTH_PASSWORD=change-me \
  -v nginx-data:/data nginx-railway
```

## Is NGINX Free to Self-Host?

NGINX is open source under a permissive two-clause BSD licence, with no paid tier, seat count or feature gate in the version deployed here. F5 sells a commercial distribution, NGINX Plus, adding active health checks and support — neither of which this template needs. Self-hosting costs only the compute and storage the container uses.

## FAQ

**What is NGINX?**
NGINX is an open-source web server, reverse proxy, load balancer and HTTP cache. It is event-driven rather than thread-per-connection, which is why one small instance handles very high concurrency.

**What does this Railway template deploy?**
One `nginx` service with a persistent volume at `/data`. It serves static files from `/data/www`, optionally proxies paths to other services, and optionally caches those responses in `/data/cache`. No database or supporting service is involved.

**Why does the template include a volume?**
Container filesystems are discarded on every deploy. The volume keeps your site and cache separate from the image, so you can redeploy to pick up an NGINX security release without touching a page.

**How do I put NGINX in front of my other Railway services?**
Set `PROXY_ROUTES` to `/path=upstream` pairs using each service's private hostname, such as `/api=http://api.railway.internal:3000`. Only NGINX needs a public domain. Upstreams are re-resolved as they change, so redeploying a backend does not strand a stale address.

**Does self-hosted NGINX see the real visitor IP behind Railway's proxy?**
Yes. Railway's edge appends its own rotating address to `X-Forwarded-For`, so the usual `real_ip_recursive` recipe lands on a Railway address. This configuration reads the leftmost entry instead, and uses it for access logs, rate limiting and the headers sent upstream.

**How do I password-protect the site?**
Set `BASIC_AUTH_USER` and `BASIC_AUTH_PASSWORD`. The htpasswd file is generated at boot, so no password is stored in the image, and `/healthz` stays anonymous so the health check keeps passing.


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/nginx
