---
title: "Deploy Nginx | (Just Updated) Static Site Host with Token Upload API, Reverse-Proxy Ready, Files Survive Redeploys"
description: "Nginx static site host: upload files over HTTP with a token, on a volume"
category: "Other"
url: https://railway.com/deploy/nginx-or-just-updated-static-site-host-w
---

# Deploy Nginx | (Just Updated) Static Site Host with Token Upload API, Reverse-Proxy Ready, Files Survive Redeploys

Nginx static site host: upload files over HTTP with a token, on a volume

**[Deploy Nginx | (Just Updated) Static Site Host with Token Upload API, Reverse-Proxy Ready, Files Survive Redeploys on Railway](https://railway.com/template/nginx-or-just-updated-static-site-host-w)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/nginx-or-just-updated-static-site-host-w/manifest.json

- **Creator:** SuperSlowSloth
- **Category:** Other

## Template content

### nginx

- **Image:** nginx:1.30.6@sha256:42c68eab6f7e22611786a9fe1d1c019e275c50185bd54b8f75dc2b9184322703
- **Start command:** `/bin/sh -c 'set -eu
# Boot script for the Railway template. Runs as root inside the stock nginx image (a start command replaces the image ENTRYPOINT).
umask 022
D=/data
ROOT="$D/www"
PORT="${PORT:-8080}"
: "${UPLOAD_TOKEN:?UPLOAD_TOKEN is not set}"
mkdir -p "$ROOT" "$D/tmp" "$D/nginx.d"
if [ ! -e "$ROOT/index.html" ]; then
cat > "$ROOT/index.html" <<"HTML"
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>Nginx on Railway</title></head>
<body><h1>Nginx is running</h1>
<p>Upload your site with <code>curl -u deploy:$UPLOAD_TOKEN -T index.html $SITE_URL/index.html</code>.</p></body></html>
HTML
fi
chown -R nginx:nginx "$D/www" "$D/tmp" "$D/nginx.d"
# The upload credential is the user deploy plus UPLOAD_TOKEN; the hash is rebuilt on every boot, so rotating the variable rotates the credential.
printf "deploy:%s\n" "$(openssl passwd -apr1 "$UPLOAD_TOKEN")" > /etc/nginx/upload.htpasswd
chgrp nginx /etc/nginx/upload.htpasswd
chmod 640 /etc/nginx/upload.htpasswd
cat > /etc/nginx/conf.d/default.conf <<"CONF"
server {
    listen __PORT__ default_server;
    listen [::]:__PORT__ default_server;
    server_name _;
    root /data/www;
    index index.html;
    server_tokens off;
    client_max_body_size 512m;
    # Uploads are written to a temp file and renamed into place, so the temp dir must sit on the same volume as the site.
    client_body_temp_path /data/tmp;
    gzip on;
    gzip_types text/plain text/css application/javascript application/json image/svg+xml;
    location = /healthz {
        access_log off;
        default_type text/plain;
        return 200 "ok\n";
    }
    location / {
        limit_except GET HEAD {
            auth_basic "upload";
            auth_basic_user_file /etc/nginx/upload.htpasswd;
        }
        dav_methods PUT DELETE MKCOL COPY MOVE;
        dav_access user:rw group:rw all:r;
        create_full_put_path on;
    }
    include /data/nginx.d/*.conf;
}
CONF
sed -i "s/__PORT__/$PORT/g" /etc/nginx/conf.d/default.conf
nginx -t
exec nginx -g "daemon off;"'`
- **Health check:** /healthz
- **Public domain:** Yes

## Documentation

# Deploy and Host Nginx on Railway

**Built for an LLM agent to deploy and use.** There is no content to bake into an image and no form to fill in: the site lives on a volume, an upload token is generated at deploy time, and the contract below lists every URL, variable name and the one script that proves it works. Publish a site with `curl`, no git repository and no redeploy.

Nginx is the open-source (BSD-2) web server and reverse proxy behind a large share of the web. This template runs the official **Nginx 1.30.6** image (stable branch, pinned by digest) as a static site host: files under `/data/www` on the volume are served on the public domain, and the built-in WebDAV module accepts `PUT`, `DELETE`, `MKCOL`, `COPY` and `MOVE` from anyone who presents the upload token.

## About Hosting Nginx

This template runs one service with a volume at `/data`. On every boot the start command writes the nginx server block and the upload credential from the service variables, so the config never drifts from them. `/data/www` is the web root (a placeholder `index.html` is written only when none exists), `/data/tmp` holds upload temp files on the same volume so a `PUT` can be renamed into place, and `/data/nginx.d` is an extension point: any `*.conf` file there is included inside the server block on every boot, which is where `location` blocks with `proxy_pass` to other services in the project go. Reads (`GET`, `HEAD`) are public; every write needs HTTP basic auth with the user `deploy` and the password `UPLOAD_TOKEN`. Rotating the variable rotates the credential on the next deploy.

### Agent contract

- Public URL: `https://` plus the `RAILWAY_PUBLIC_DOMAIN` of the `nginx` service, also published as the variable `SITE_URL`
- Private URL: `http://nginx.railway.internal:8080` (also the variable `SITE_PRIVATE_URL`), for other services in the project
- Credentials: `UPLOAD_TOKEN` (user `deploy`). It stays the same across redeploys.
- Auth: HTTP basic auth, `-u deploy:$UPLOAD_TOKEN`, on every method except `GET` and `HEAD`. A write with no or a wrong credential returns 401.
- Health: `GET /healthz` returns 200 with the body `ok`, no credentials

```bash
# run with SITE_URL and UPLOAD_TOKEN set
curl -s -o /dev/null -w 'health %{http_code}\n' "$SITE_URL/healthz"
curl -s -o /dev/null -w 'put-no-credential %{http_code}\n' -X PUT -d x "$SITE_URL/docs/a.txt"
curl -s -o /dev/null -w 'put-wrong-token %{http_code}\n' -u deploy:wrong -X PUT --data-binary 'hello from the agent' "$SITE_URL/docs/a.txt"
curl -s -o /dev/null -w 'put-with-token %{http_code}\n' -u "deploy:$UPLOAD_TOKEN" -X PUT --data-binary 'hello from the agent' "$SITE_URL/docs/a.html"
curl -s -o /dev/null -w 'put-index %{http_code}\n' -u "deploy:$UPLOAD_TOKEN" -X PUT --data-binary 'hello from the agent' "$SITE_URL/index.html"
curl -s "$SITE_URL/docs/a.html"
curl -s "$SITE_URL/" | grep -o 'hello from the agent'
curl -s -o /dev/null -w 'missing %{http_code}\n' "$SITE_URL/nope"
curl -s -o /dev/null -w 'delete-no-credential %{http_code}\n' -X DELETE "$SITE_URL/docs/a.html"
curl -s -o /dev/null -w 'delete-with-token %{http_code}\n' -u "deploy:$UPLOAD_TOKEN" -X DELETE "$SITE_URL/docs/a.html"
curl -s -o /dev/null -w 'after-delete %{http_code}\n' "$SITE_URL/docs/a.html"
```

A fresh deploy prints `health 200`, `put-no-credential 401`, `put-wrong-token 401`, `put-with-token 201`, `put-index 204` (the placeholder page is replaced; `201` if you removed it first), the uploaded HTML twice (the file and the site root), `missing 404`, `delete-no-credential 401`, `delete-with-token 204` and `after-delete 404`. Upload a whole site with a loop of `curl -u deploy:$UPLOAD_TOKEN -T FILE $SITE_URL/PATH`; missing directories are created.

## Why Deploy Nginx on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Nginx on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

- **Agent-ready** - generated upload token, a contract block and one working script; no wizard and no repository to fork.
- **Update the site without a redeploy** - upload, replace and delete files over HTTP while the service keeps running.
- **Files survive redeploys** - the web root sits on a volume, so uploaded content is still there after a restart.
- **Reverse-proxy ready** - drop a `.conf` in `/data/nginx.d` to proxy a path to another service over Railway private networking.
- **Pinned image** - pinned by digest, so a redeploy never changes the server version underneath you.

## Common Use Cases

- **Static site and landing page hosting** - HTML, CSS, JavaScript and images served by nginx with gzip on.
- **Artifact and file hosting** - an agent pushes build output, reports or datasets and shares the public URL.
- **Reverse proxy in front of other services** - route paths to internal services from one public domain.

## Dependencies for Nginx Hosting

- Nothing else: the site, upload temp files and extra config all live on the volume at `/data`.

### Deployment Dependencies

- [nginx/nginx](https://github.com/nginx/nginx) - upstream project
- [Nginx documentation](https://nginx.org/en/docs/) - server blocks, proxying and the WebDAV module


## 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-or-just-updated-static-site-host-w
