🇯🇵 Tokyo is live! 🚀 Launch your VPS and enjoy 2 months off — use code KONNICHIWA50 🎉 Get Started Today →

VPSus Self-Hosting Guide: Setting Up Your MCP Server

Stack of servers connected to a cloud and a padlock, symbolizing secure cloud data storage on a dark background.

The Model Context Protocol (MCP) is what lets Claude, Cursor, and other AI clients reach into your own tools, data, and internal APIs. Hosted MCP services are convenient, but they sit in the middle of every request — seeing your prompts, your credentials, and the results coming back. Running your own MCP server on a VPS removes that middleman entirely. Your queries stay private, your API keys never leave your infrastructure, and you can run any MCP server you want without per-seat pricing or vendor limits.

This guide walks through the full setup end to end: provisioning a VPS, deploying the MCP container with Docker Compose, putting it behind an NGINX reverse proxy with Let’s Encrypt TLS, locking down the firewall and SSH, and pointing your AI client at the new endpoint. By the time you reach Step 9, you’ll have a production-grade MCP server reachable at your own domain, running on hardware you control, with backups and a clear path to scale.

Step 1: Confirm Your Prerequisites

Before you touch a terminal, ensure you have the essential accounts and tools this setup depends on. This prevents the most common self-hosting pitfall: halting deployment midway due to missing DNS, image pulls, or container tooling. For a single MCP server handling light to moderate AI workflow traffic, 1 vCPU and 2 GB RAM is a practical baseline. That’s enough for Docker, a reverse proxy, and the MCP container without the box constantly swapping to disk — a scenario where a “cheap VPS” feels expensive.

RequirementMinimumWhy you need itWhat to check now
VPS accountActive accountYou need a Linux server with root or sudo accessCan you deploy an Ubuntu 22.04 or Debian 12 VPS?
Domain registrarOne domain/subdomainNeeded for a stable hostname and HTTPS laterDo you control DNS for mcp.yourdomain.com?
Container registry accountDocker Hub accountUseful for pulling public images and avoiding pull limitsCan you log in at hub.docker.com?
VPS size1 vCPU, 2 GB RAMBaseline for one MCP container plus proxy overheadConfirm the instance has at least 20 GB SSD
Docker Engine20.10+Required to run the MCP container reliablydocker --version
Docker Compose1.29+ or Compose plugin v2Needed if the deployment uses compose.ymldocker-compose --version or docker compose version
SSH clientAny modern clientYou’ll manage the server over SSHTest login from your laptop

The key takeaway: if you have these seven items ready, the rest of the setup is straightforward terminal work rather than account-recovery busywork. If anything in the table is missing, fix that first. With everything prepared, the next step is choosing and provisioning the VPS.

⚡ Spin up a Premium VPS in 2 minutes
17 locations worldwide
NVMe  Â·  Unmetered 1 Gbps  Â·  Full root access  Â·  From $10/mo
Pick Your Location →

Step 2: Provision Your VPS Instance

For MCP, region choice matters more than raw CPU. If your app backend or AI client mostly accesses APIs from New York, Ashburn, or Toronto, place your VPS on the same side of the ocean. Aim for under 50 ms round-trip latency to keep request/response overhead minimal. As a general guide, same-city or same-region traffic often falls in the 5–20 ms range, same-continent around 20–50 ms, and cross-ocean connections might hit 80–150+ ms.

Use your provider’s panel to create a KVM VPS with your Linux image from Step 1. In the region picker, match the server’s location to where your MCP clients run, not necessarily where you live. For example, if your automation workers run in Frankfurt and your laptop is in Chicago, Frankfurt is typically the right choice. If your users are in Southeast Asia, Singapore will generally outperform any US region by reducing triple-digit millisecond delays.

Most panels ask for five key details: region, OS image, plan size, hostname, and SSH access. Choose Ubuntu 22.04 LTS or Debian 12, stick with the baseline size from Step 1, and add your SSH public key during provisioning. This provides a faster, safer passwordless login rather than enabling root password authentication.

The expected output should show your instance as running with a public IPv4 address. After boot, note the default SSH user. On Ubuntu it is commonly ubuntu; on Debian, debian or root may be used depending on the image. If the image disables direct root login, that’s normal — use the default user and escalate privileges with:

sudo -i

Your shell prompt should change to root@your-hostname, confirming that root privileges are available for subsequent installation steps.

Step 3: Install Docker and Dependencies

Once your VPS is live, the next step is to install the runtime that will host the MCP service. Containers resolve the “it works on my machine” problem by packaging the app with its dependencies, ensuring that your server runs the same image tested by the author. This section covers updating the OS, installing Docker Engine, adding the Compose plugin, and enabling Docker to start on boot.

sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

Verify Docker and Compose installation with:

docker --version
docker compose version
sudo systemctl status docker --no-pager

If you prefer a Docker VPS hosting setup for efficient container management, this is the ideal method. For RHEL-compatible systems, use the native package manager outlined in the article.

To avoid prepending sudo for every Docker command, add your SSH user to the docker group:

sudo usermod -aG docker "$USER"
newgrp docker
docker ps

Verifying that docker ps works without sudo confirms that the environment is correctly set up for the MCP container.

Step 4: Deploy the MCP Server Container

With Docker operational, the next task is to ensure that your MCP server container starts reliably, restarts on reboots, and handles load predictably. Using Docker Compose is preferred over ad-hoc commands; it consolidates your configuration into one file that can be versioned and redeployed easily.

Create a new directory and add a docker-compose.yml file as follows:

services:
  mcp-server:
    image: ghcr.io/example/mcp-server:1.2.0
    container_name: mcp-server
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      API_KEY: "replace-with-a-long-random-key"
      PORT: "3000"
      ALLOWED_ORIGINS: "https://app.yourdomain.com,https://admin.yourdomain.com"
    mem_limit: 768m
    cpus: 0.75

This configuration prevents the common “works once, breaks on reboot” issue while setting resource limits. The API_KEY is the shared secret used for client authentication, PORT indicates the internal bind port, and ALLOWED_ORIGINS sets a comma-separated allowlist to restrict browser-based requests. The bind of 127.0.0.1:3000:3000 keeps the app private to the VPS, pending the reverse proxy configuration.

The specified memory and CPU limits ensure that on a 2 GB VPS, the container reserves enough headroom for the OS, Docker, and any reverse proxy you introduce later. To run the container in the background, execute:

mkdir -p ~/mcp-server && cd ~/mcp-server
nano docker-compose.yml
docker compose up -d

Verify the deployment with:

docker compose ps
docker logs --tail=50 mcp-server

For a comparison between single container and Compose deployments, keep in mind that Compose supports repeatability and versioning — a considerable advantage over one-off docker run commands. Also, pin your image to a specific version, such as :1.2.0, to prevent issues caused by silent upstream changes.

Step 5: Configure a Reverse Proxy and SSL

Since the container listens only on 127.0.0.1:3000, it is inaccessible externally. The solution is to set up an NGINX reverse proxy that accepts public traffic, forwards MCP-specific requests to the container, and terminates TLS. First, update your DNS A record for mcp.yourdomain.com to point to your VPS’s IP address, then install NGINX and Certbot:

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx

Verify installation with:

nginx -v
certbot --version

The following NGINX server block binds your custom domain, proxies MCP requests, and redirects HTTP traffic to HTTPS:

server {
    listen 80;
    listen [::]:80;
    server_name mcp.yourdomain.com;

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name mcp.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;

    location /mcp/ {
        proxy_pass http://127.0.0.1:3000/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Save the configuration as /etc/nginx/sites-available/mcp and enable it:

sudo nano /etc/nginx/sites-available/mcp
sudo ln -s /etc/nginx/sites-available/mcp /etc/nginx/sites-enabled/mcp
sudo nginx -t && sudo systemctl reload nginx

Verify the reverse proxy:

curl -I http://mcp.yourdomain.com/mcp/

After confirming a 301 Moved Permanently response, issue the TLS certificate using Certbot:

sudo certbot --nginx -d mcp.yourdomain.com

This resolves self-signed certificate warnings and sets up auto-renewal. Verify HTTPS end-to-end with:

curl -I https://mcp.yourdomain.com/mcp/
systemctl list-timers | grep certbot

For more insights into setting up self-hosted services, consider reading our article on Mastering Self-Hosting: Take Control of Your Apps and Data.

Step 6: Harden Your Server Security

A 3D illustration of a server with a shield, heart monitor, and bell icons, symbolizing security and notifications.

Your MCP endpoint is now accessible over HTTPS. To safeguard it, limit open ports to only the essentials — typically three: SSH for admin access, HTTP for certificate renewals, and HTTPS for client traffic. Implement a default-deny firewall with UFW via:

sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH admin access'
sudo ufw allow 80/tcp comment 'HTTP for redirect and ACME challenge'
sudo ufw allow 443/tcp comment 'HTTPS for MCP endpoint'
sudo ufw enable
sudo ufw status numbered

Confirm that only ports 22, 80, and 443 are open with:

sudo ufw status verbose
ss -tulpn

Next, use fail2ban to block SSH brute-force attacks:

sudo apt install -y fail2ban
sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'
[sshd]
enabled = true
port = 22
logpath = %(sshd_log)s
maxretry = 5
findtime = 10m
bantime = 1h
EOF
sudo systemctl enable --now fail2ban

Verify with:

sudo fail2ban-client status sshd

Finally, disable direct root SSH login and require key-based authentication by editing /etc/ssh/sshd_config:

# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Apply and verify the SSH configuration changes:

sudo sshd -t && sudo systemctl reload ssh
grep -E 'PermitRootLogin|PasswordAuthentication|PubkeyAuthentication' /etc/ssh/sshd_config

Step 7: Connect Your MCP Client

With the server secured and reachable via HTTPS, point your MCP client at it so your automations transition from localhost to the newly built VPS endpoint. For Claude Desktop, update the MCP client JSON to reflect your domain and API key. For example, if the reverse proxy exposes the service at https://mcp.yourdomain.com/mcp/, the configuration might be:

{
  "mcpServers": {
    "private-vps-mcp": {
      "transport": {
        "type": "http",
        "url": "https://mcp.yourdomain.com/mcp/"
      },
      "headers": {
        "Authorization": "Bearer REPLACE_WITH_YOUR_LONG_API_KEY"
      }
    }
  }
}

After saving the configuration in your client’s MCP settings, test connectivity with:

curl -i https://mcp.yourdomain.com/mcp/ \
  -H "Authorization: Bearer REPLACE_WITH_YOUR_LONG_API_KEY" \
  -H "Content-Type: application/json"

A successful response (e.g., 200 OK or 202 Accepted) confirms proper connectivity to your MCP server.

Step 8: Verify Operation and Monitor

A 3D illustration of a server with a shield, heart monitor, and bell icons, symbolizing security and notifications.

Before going live, verify that your endpoint responds over HTTPS, the container remains healthy, and you’ll be promptly alerted if it goes down. Perform a 30-second check to avoid discovering issues only after an automation fails.

curl -i https://mcp.yourdomain.com/mcp/health
# or
http https://mcp.yourdomain.com/mcp/health

docker logs --tail=100 mcp-server
docker ps --filter "name=mcp-server"
docker restart mcp-server

For external monitoring, tools like UptimeRobot can check your health URL every minute or every five minutes for non-200 responses. This not only confirms that your MCP container is running but also that it’s ready to support your operations. Additionally, our guide on Self Hosted Backup Software: Tools, Setup, and Best Practices provides further tips on maintaining operational resiliency.

Step 9: Next Steps and Cleanup

Once your health checks remain stable, focus on two aspects that make self-hosting sustainable: recoverability and easy scaling. Create a backup of your current Docker configuration and application data before making major updates:

mkdir -p ~/backups/mcp-$(date +%F)
cp docker-compose.yml ~/backups/mcp-$(date +%F)/
docker inspect mcp-server > ~/backups/mcp-$(date +%F)/container-inspect.json
tar -czf ~/backups/mcp-$(date +%F)/mcp-data.tar.gz /opt/mcp-data 2>/dev/null || true

Verify your backup with:

ls -lah ~/backups/mcp-$(date +%F)

For scaling, if one container becomes a bottleneck, horizontally scale behind NGINX by running:

docker compose up -d --scale mcp-server=3
docker ps --filter "name=mcp-server"

When necessary, safely tear down the setup:

docker compose down
docker image prune -f
docker volume prune -f

Verify cleanup with:

docker ps -a
docker system df

This comprehensive guide ensures you can set up, secure, monitor, and scale your MCP server confidently on a VPS using self-hosting best practices.

Frequently Asked Questions

What are the minimum server requirements for MCP setup on a VPS?

You need at least 1 vCPU, 2 GB RAM, and 20 GB SSD, along with Docker Engine, Docker Compose, a VPS account, and domain control.

How do I secure my self-hosting environment after deployment?

Implement a strict firewall (UFW), use fail2ban for SSH, disable root login, and enforce key-based SSH authentication.

Why should I use Docker Compose instead of a single container deployment?

Docker Compose centralizes your configuration, making deployments repeatable, version-controlled, and easier to scale or modify.

How can I verify that my MCP container is running properly?

Check the container status with docker ps, review logs with docker logs, and test the health endpoint using curl.

What are the benefits of deploying MCP on a VPS using a reverse proxy?

A reverse proxy, like NGINX, enables HTTPS termination, hides internal ports, and enhances security while providing scalability and load balancing.
Facebook
Twitter
LinkedIn

Table of Contents

Get started today

With VPS.US VPS Hosting you get all the features, tools

Image