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

📚 More from the Blog