Deployment10 min read

Deploying MCP Servers to Railway: A Complete Guide

Step-by-step guide to deploying MCP servers on Railway. Covers project setup, persistent transport, environment secrets, auto-deploy from GitHub, and production best practices.

By MyMCPTools Team·

Cloudflare Workers and AWS Lambda are great for stateless MCP tools, but some tools need to stay alive between requests — maintaining a database connection, caching results in memory, or holding an authenticated session. Railway is purpose-built for exactly this kind of always-on, persistent workload.

Railway deploys your code as a long-running process (not a Lambda function), gives you a private network for your databases, and auto-deploys from GitHub with zero configuration. It's the fastest way to get a production-grade MCP server running in under 30 minutes.

Why Railway for MCP Servers

Railway's model fits MCP servers better than serverless platforms when you need:

  • Persistent connections — keep a database connection pool alive, maintain WebSocket state, hold authenticated sessions across tool calls
  • Long-running operations — no 30-second function timeout; your tools can run for minutes
  • Private networking — your MCP server and its Postgres/Redis databases communicate over Railway's private network, never the public internet
  • Faster cold starts — your process is already running, no cold start penalty on the first tool call

Step 1: Create Your MCP Server

Start with a Node.js MCP server using the official SDK. Create a new project:

mkdir my-mcp-railway && cd my-mcp-railway
npm init -y
npm install @modelcontextprotocol/sdk express

Create server.js:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

const app = express();
const server = new McpServer({
  name: "My Railway MCP Server",
  version: "1.0.0",
});

// Register your tools
server.tool(
  "get_timestamp",
  "Get the current server timestamp",
  {},
  async () => ({
    content: [{ type: "text", text: new Date().toISOString() }],
  })
);

// SSE transport endpoint
app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/message", res);
  await server.connect(transport);
});

app.post("/message", express.json(), async (req, res) => {
  res.json({ status: "ok" });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`MCP server running on port ${PORT}`);
});

Add "type": "module" to your package.json and a start script:

{
  "type": "module",
  "scripts": {
    "start": "node server.js"
  }
}

Step 2: Deploy to Railway

The fastest path is GitHub-connected deployment:

  1. Push your project to a GitHub repository
  2. Go to railway.app and create a new project
  3. Select Deploy from GitHub repo and choose your repository
  4. Railway auto-detects Node.js and configures the build — click Deploy Now

Within 2 minutes, your server is live. Railway assigns a public URL in the format https://your-project.up.railway.app.

For CLI-based deployment:

npm install -g @railway/cli
railway login
railway init
railway up

Step 3: Environment Variables and Secrets

Set secrets through the Railway dashboard (Variables tab) or CLI — never commit them to your repo:

# Via CLI
railway variables set DATABASE_URL=postgresql://...
railway variables set API_KEY=sk-...
railway variables set MCP_AUTH_TOKEN=your-secret-token

Access them in your server code via process.env.VARIABLE_NAME. Railway injects them at deploy time and restarts your service automatically when variables change.

Step 4: Add a Postgres Database

One of Railway's killer features is one-click databases that live on the same private network as your server:

  1. In your project, click + NewDatabaseAdd PostgreSQL
  2. Railway provisions the database and automatically sets DATABASE_URL in your service's environment
  3. Your MCP server can now connect without exposing the database to the public internet
import pg from "pg";

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false,
});

server.tool(
  "query_database",
  "Run a read-only SQL query",
  { sql: { type: "string", description: "SELECT query to run" } },
  async ({ sql }) => {
    if (!sql.trim().toLowerCase().startsWith("select")) {
      return { content: [{ type: "text", text: "Only SELECT queries are allowed" }] };
    }
    const result = await pool.query(sql);
    return {
      content: [{ type: "text", text: JSON.stringify(result.rows, null, 2) }],
    };
  }
);

Step 5: Authentication

Protect your Railway MCP server with a bearer token check before processing any request:

app.use((req, res, next) => {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (token !== process.env.MCP_AUTH_TOKEN) {
    return res.status(401).json({ error: "Unauthorized" });
  }
  next();
});

In your MCP client configuration (e.g., Claude Desktop's claude_desktop_config.json), pass the token as a header:

{
  "mcpServers": {
    "my-railway-server": {
      "url": "https://your-project.up.railway.app/sse",
      "headers": {
        "Authorization": "Bearer your-secret-token"
      }
    }
  }
}

Step 6: Auto-Deploy on Push

Railway redeploys automatically on every push to your connected GitHub branch. For production servers, create a deployment rule that only triggers on pushes to main:

  1. Go to your service settings → Deployments
  2. Set the Watch Paths to trigger only on relevant file changes
  3. Enable Deployment Protection for your production environment

Zero-downtime deploys are enabled by default — Railway keeps your old container running until the new one passes its health check.

Health Checks and Monitoring

Add a health endpoint so Railway knows when your server is ready:

app.get("/health", (req, res) => {
  res.json({ status: "ok", uptime: process.uptime() });
});

In your Railway service settings, set the health check path to /health. Railway will route traffic to your new deploy only after this endpoint returns 200.

Pricing and Scaling

Railway's free tier gives you $5 of compute credits per month — enough to run a lightweight MCP server continuously for most personal projects. The Hobby plan ($5/month) removes the credit limit and adds persistent volumes.

For teams, the Pro plan adds shared environments, team permissions, and priority support. Most MCP servers — even ones serving 10-20 users — run comfortably on a single $5/month service.

Railway vs. Other MCP Hosting Options

  • Railway vs. Cloudflare Workers — Railway wins for stateful tools (DB connections, sessions, caching). Cloudflare wins for globally distributed, stateless tools.
  • Railway vs. AWS Lambda — Railway is significantly simpler to set up and debug; Lambda has more ecosystem depth for enterprise workloads.
  • Railway vs. Vercel — Vercel's serverless functions time out at 300s max; Railway processes run indefinitely, making it better for long-running MCP operations.

For most MCP servers that need a database, Railway is the fastest path from code to production. Browse the database MCP servers in our directory for tools you can pair with your Railway deployment.

Recommended Tools

Better Stack

Free Plan

Get alerted when your APIs, browser tests, payment pipelines, or MCP server dependencies go down. Used by 100K+ developers.

Start monitoring free →

1Password

14-day Free Trial

Store and inject API keys, payment credentials, tokens, and file access secrets into your MCP server configs. Trusted by 150K+ developers.

Try 1Password free →

🔧 MCP Servers Mentioned in This Article

📁

Filesystem MCP Server

sandboxed read, write, edit, move and search access to an explicit whitelist of local directories, and it is the reference implementation most other filesystem MCP servers are modelled on. Shipped by Anthropic in the official modelcontextprotocol/servers monorepo (89,000+ stars, actively maintained), it is a Node.js server published to npm as @modelcontextprotocol/server-filesystem. The part worth understanding before you install is the access-control model, because there are now two ways to grant directories and they do not compose. Method one is command-line arguments: `npx -y @modelcontextprotocol/server-filesystem /path/one /path/two`. Method two, and the one the maintainers recommend, is MCP Roots — a client that supports the roots protocol sends its roots at initialization, and those roots COMPLETELY REPLACE any directories passed on the command line, then get replaced again on every `notifications/roots/list_changed`. That means allowed directories can change at runtime without restarting the server, but it also means a roots-capable client silently overrides your CLI arguments. If the server starts with no arguments and the client either does not support roots or sends an empty list, initialization throws an error. The tool surface is broad: `read_text_file` (with mutually exclusive `head`/`tail` line windows), `read_media_file` returning base64 image/audio content blocks, `read_multiple_files` which keeps going when individual reads fail, `write_file`, `edit_file`, `create_directory`, `list_directory`, `list_directory_with_sizes`, `move_file`, `search_files`, `directory_tree`, `get_file_info` and `list_allowed_directories`. `edit_file` is the one to learn — it does line-based and multi-line pattern matching with indentation detection and preservation, returns a git-style diff with context, and supports `dryRun: true` so you can preview a change before applying it; the maintainers recommend always running a dry run first. Every operation is refused outside the allowed set, and `list_allowed_directories` is the fastest way to confirm what the server actually believes it can touch.

Local
💻

GitHub MCP Server

authenticated access to the whole GitHub platform — repositories, files, branches, issues, pull requests, Actions runs, security alerts, discussions and notifications — from Claude, Cursor, VS Code, Copilot CLI and any other MCP host. There is no npm package for this server, and that trips up most people who try to install it: `@github/mcp-server` is not published to the npm registry, so any `npx` line you find for it will fail. GitHub ships it three other ways. The easiest is the hosted remote server at https://api.githubcopilot.com/mcp/, which needs no install at all — point an HTTP-transport MCP client at that URL and log in with OAuth (VS Code 1.101+, Claude Desktop, Claude Code, Cursor and Windsurf all support this). The second is the official Docker image ghcr.io/github/github-mcp-server, which is what the copy-paste command on this page runs; on github.com it now performs a browser-based OAuth login on first use and keeps the token in memory only, which is why the published Docker configs map a fixed loopback callback port (-p 127.0.0.1:8085:8085 with GITHUB_OAUTH_CALLBACK_PORT=8085) so the container can receive the callback. Prefer a token? Set GITHUB_PERSONAL_ACCESS_TOKEN instead — it takes precedence over OAuth, and the minimum useful scopes are repo, read:org and read:packages. The third is the native Go binary from the repository's releases, which needs no fixed port for the OAuth flow. GitHub Enterprise Server has no hosted option: use the local server with --gh-host or GITHUB_HOST set to your instance (include the https:// scheme — it defaults to http://, which GHES rejects). Toolsets can be narrowed with GITHUB_TOOLSETS, and an insiders channel is available at /mcp/insiders or via the X-MCP-Insiders header.

Auth required📘
🗄️

Redis MCP Server

The Redis MCP Server (redis/mcp-redis) is Redis's own natural-language interface for agentic applications, letting an AI client read and write Redis data over the Model Context Protocol. Note which one you install: the server most tutorials still point at is Anthropic's reference implementation, which now lives in modelcontextprotocol/servers-archived, and its npm package @modelcontextprotocol/server-redis is explicitly marked "Package no longer supported" with a last publish of 2025-04-25. The maintained server is a Python package instead, run with uvx --from redis-mcp-server@latest, and it covers far more of Redis than the reference one did: string, hash, list, set and sorted-set tools; JSON document tools; pub/sub with stateful channel and pattern subscriptions; Streams tools including consumer-group create, read, acknowledge and destroy; vector index management and vector search through the query engine; a docs search tool; and a server-management tool for database info. Connection is a redis:// or rediss:// URL passed as --url, or the REDIS_HOST/REDIS_PORT/REDIS_PWD/REDIS_SSL environment variables, with Redis Cluster mode behind REDIS_CLUSTER_MODE and EntraID service-principal, managed-identity and default-credential auth flows for Azure Managed Redis. There is no --read-only flag: the documented way to stop an agent writing is a Redis ACL user (ACL SETUSER readonlyuser on >pw ~* +@read -@write). Ships as a PyPI package, a GitHub install via uvx, and an official mcp/redis Docker image; stdio transport only.

Local📘

📚 More from the Blog