- Renamed all directories: OpenHarbor.MCP.* → Svrnty.MCP.* - Updated all namespaces in 179 C# files - Renamed 20 .csproj files and 3 .sln files - Updated 193 documentation references - Updated 33 references in main CODEX codebase - Updated Codex.sln with new paths - Build verified: 0 errors Preparing for extraction to standalone repositories. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
16 KiB
HTTPS/TLS Setup Guide - Svrnty.MCP.Server
Purpose: Production-grade HTTPS/TLS configuration for secure MCP server deployment Audience: DevOps engineers, system administrators Last Updated: 2025-10-19 Version: 1.0.0
Table of Contents
- Overview
- Prerequisites
- Development Setup
- Production Setup
- Certificate Management
- Security Headers (HSTS)
- Testing HTTPS
- Troubleshooting
Overview
Svrnty.MCP.Server supports HTTPS/TLS through ASP.NET Core's Kestrel web server. This guide covers configuration for both development and production environments.
Security Benefits:
- Encrypted communication (prevents eavesdropping)
- Client/server authentication
- Data integrity verification
- Compliance with security best practices
Default Behavior:
- HTTP only (for development ease)
- HTTPS must be explicitly configured
- Uses Kestrel's built-in TLS support
Prerequisites
Development
- .NET 8.0 SDK
- OpenSSL or dotnet dev-certs tool
- PowerShell (Windows) or bash (Linux/macOS)
Production
- Valid TLS certificate (from CA or Let's Encrypt)
- Private key file
- Certificate chain (intermediate + root CA certs)
- Reverse proxy (optional): Nginx, Traefik, or Kestrel standalone
Development Setup
Option 1: ASP.NET Core Development Certificate (Recommended)
Step 1: Generate Development Certificate
Windows / macOS / Linux:
# Generate and trust dev certificate
dotnet dev-certs https --trust
# Verify installation
dotnet dev-certs https --check
Step 2: Configure appsettings.Development.json
Create or update appsettings.Development.json in the Server project:
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5050"
},
"Https": {
"Url": "https://localhost:5051",
"Certificate": {
"Subject": "CN=localhost",
"Store": "My",
"Location": "CurrentUser",
"AllowInvalid": true
}
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Step 3: Run Server with HTTPS
cd Svrnty.MCP.Server/samples/CodexMcpServer
dotnet run --environment Development
# Server will listen on:
# - HTTP: http://localhost:5050
# - HTTPS: https://localhost:5051
Step 4: Test HTTPS Endpoint
# Test health endpoint
curl -k https://localhost:5051/health
# Test MCP invoke endpoint
curl -k -X POST https://localhost:5051/mcp/invoke \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": "1"
}'
Note
:
-kflag skips certificate validation (dev only!)
Option 2: Custom Development Certificate with OpenSSL
Step 1: Generate Self-Signed Certificate
# Create directory for certs
mkdir -p /home/svrnty/codex/Svrnty.MCP.Server/certs
# Generate private key
openssl genrsa -out certs/localhost.key 2048
# Generate certificate signing request (CSR)
openssl req -new -key certs/localhost.key -out certs/localhost.csr \
-subj "/C=CA/ST=Quebec/L=Montreal/O=Svrnty/CN=localhost"
# Generate self-signed certificate (valid for 365 days)
openssl x509 -req -days 365 -in certs/localhost.csr \
-signkey certs/localhost.key -out certs/localhost.crt
# Convert to PFX format (required by Kestrel)
openssl pkcs12 -export -out certs/localhost.pfx \
-inkey certs/localhost.key -in certs/localhost.crt \
-passout pass:YourSecurePassword
Step 2: Configure appsettings.Development.json
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:5051",
"Certificate": {
"Path": "certs/localhost.pfx",
"Password": "YourSecurePassword"
}
}
}
}
}
Security: Add certs/ directory to .gitignore to avoid committing private keys.
Production Setup
Option 1: Certificate from File (Recommended for Kubernetes/Docker)
Step 1: Obtain Production Certificate
Use one of:
- Let's Encrypt (free, automated renewal)
- Commercial CA (DigiCert, Sectigo, etc.)
- Internal PKI (corporate certificate authority)
Step 2: Prepare Certificate Files
Ensure you have:
server.crt- Server certificateserver.key- Private keyca-bundle.crt- Intermediate + root CA certificates (optional)
Step 3: Convert to PFX Format
# Combine cert + key + chain into PFX
openssl pkcs12 -export -out server.pfx \
-inkey server.key \
-in server.crt \
-certfile ca-bundle.crt \
-passout pass:ProductionSecurePassword
Step 4: Deploy Certificate
Docker/Kubernetes:
# Create Kubernetes secret
kubectl create secret generic mcp-server-tls \
--from-file=server.pfx=server.pfx \
--from-literal=password=ProductionSecurePassword
# Mount in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
template:
spec:
containers:
- name: mcp-server
volumeMounts:
- name: tls-cert
mountPath: /app/certs
readOnly: true
env:
- name: CERTIFICATE_PASSWORD
valueFrom:
secretKeyRef:
name: mcp-server-tls
key: password
volumes:
- name: tls-cert
secret:
secretName: mcp-server-tls
Step 5: Configure appsettings.Production.json
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://*:5051",
"Certificate": {
"Path": "/app/certs/server.pfx",
"Password": "" // Read from environment variable
},
"Protocols": "Http1AndHttp2"
}
}
},
"AllowedHosts": "*"
}
Step 6: Set Environment Variable for Password
export KESTREL__CERTIFICATES__DEFAULT__PASSWORD="ProductionSecurePassword"
Or in docker-compose.yml:
services:
mcp-server:
environment:
- KESTREL__CERTIFICATES__DEFAULT__PASSWORD=${CERTIFICATE_PASSWORD}
volumes:
- ./certs/server.pfx:/app/certs/server.pfx:ro
Option 2: Certificate from Azure Key Vault
Step 1: Store Certificate in Key Vault
az keyvault certificate import \
--vault-name your-keyvault \
--name mcp-server-cert \
--file server.pfx \
--password ProductionSecurePassword
Step 2: Configure appsettings.Production.json
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://*:5051",
"Certificate": {
"KeyVaultUri": "https://your-keyvault.vault.azure.net/",
"CertificateName": "mcp-server-cert"
}
}
}
}
}
Step 3: Grant Managed Identity Access
az keyvault set-policy \
--name your-keyvault \
--object-id <managed-identity-object-id> \
--certificate-permissions get
Option 3: Reverse Proxy (Nginx/Traefik) with TLS Termination
Best for: Load balancing, multiple servers, centralized certificate management
Architecture:
Client → HTTPS → Nginx/Traefik (TLS termination) → HTTP → MCP Server (5050)
Nginx Configuration (/etc/nginx/sites-available/mcp-server):
server {
listen 443 ssl http2;
server_name mcp.example.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://localhost:5050;
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 $scheme;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name mcp.example.com;
return 301 https://$server_name$request_uri;
}
Traefik Configuration (docker-compose.yml):
services:
traefik:
image: traefik:v2.10
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
mcp-server:
image: openharbor/mcp-server:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.mcp.rule=Host(`mcp.example.com`)"
- "traefik.http.routers.mcp.entrypoints=websecure"
- "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
- "traefik.http.services.mcp.loadbalancer.server.port=5050"
Certificate Management
Automatic Renewal with Let's Encrypt
Using Certbot:
# Install certbot
sudo apt-get install certbot
# Obtain certificate
sudo certbot certonly --standalone -d mcp.example.com
# Certificates location: /etc/letsencrypt/live/mcp.example.com/
# - fullchain.pem (certificate + chain)
# - privkey.pem (private key)
# Convert to PFX for Kestrel
sudo openssl pkcs12 -export \
-out /etc/letsencrypt/live/mcp.example.com/server.pfx \
-inkey /etc/letsencrypt/live/mcp.example.com/privkey.pem \
-in /etc/letsencrypt/live/mcp.example.com/fullchain.pem \
-passout pass:YourSecurePassword
# Auto-renewal (certbot creates cron job automatically)
sudo certbot renew --dry-run
Certificate Rotation
Step 1: Update Certificate File
Replace server.pfx with new certificate.
Step 2: Reload Without Downtime
# Docker
docker exec mcp-server kill -HUP 1
# Kubernetes
kubectl rollout restart deployment/mcp-server
# Systemd
sudo systemctl reload mcp-server
Step 3: Verify New Certificate
echo | openssl s_client -connect mcp.example.com:443 -servername mcp.example.com 2>/dev/null | openssl x509 -noout -dates
Security Headers (HSTS)
HTTP Strict Transport Security (HSTS) forces browsers to use HTTPS.
Method 1: Kestrel Middleware (Recommended)
Update Program.cs in CodexMcpServer:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Add HSTS middleware (production only)
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
// Redirect HTTP to HTTPS
app.UseHttpsRedirection();
// ... rest of configuration
app.Run();
Method 2: Custom Middleware
app.Use(async (context, next) =>
{
if (!context.Request.IsHttps)
{
var httpsUrl = $"https://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}";
context.Response.Redirect(httpsUrl, permanent: true);
return;
}
// Add HSTS header
context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
await next();
});
Method 3: Reverse Proxy (Nginx)
Already shown in Option 3 above.
Testing HTTPS
1. Health Check
curl https://mcp.example.com/health
Expected output:
{
"status": "Healthy",
"service": "MCP Server",
"timestamp": "2025-10-19T12:00:00Z"
}
2. MCP Invoke Endpoint
curl -X POST https://mcp.example.com/mcp/invoke \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": "test-1"
}'
3. TLS Configuration Check
# Check TLS version and cipher suite
nmap --script ssl-enum-ciphers -p 443 mcp.example.com
# Check certificate validity
echo | openssl s_client -connect mcp.example.com:443 -servername mcp.example.com 2>/dev/null | openssl x509 -noout -text
4. HSTS Validation
curl -I https://mcp.example.com/health | grep -i strict-transport-security
Expected output:
Strict-Transport-Security: max-age=31536000; includeSubDomains
5. SSL Labs Test
For public-facing servers, use SSL Labs to get an A+ rating.
Troubleshooting
Issue: "Unable to configure HTTPS endpoint"
Symptom:
Unhandled exception. System.InvalidOperationException: Unable to configure HTTPS endpoint.
Solution:
- Verify certificate file exists at specified path
- Check file permissions (readable by server process)
- Verify password is correct
- Check certificate format (must be PFX for Kestrel)
# Verify PFX file
openssl pkcs12 -info -in server.pfx -noout
Issue: "The certificate chain was issued by an authority that is not trusted"
Symptom: Clients reject the certificate as untrusted.
Solution:
- Ensure CA bundle is included in PFX
- Add intermediate certificates to certificate store
- For development, use
dotnet dev-certs https --trust
Issue: "Connection reset" or "SSL handshake failed"
Symptom: Client cannot establish TLS connection.
Solution:
- Check firewall allows port 443
- Verify TLS protocols match (client vs. server)
- Check cipher suite compatibility
# Test with specific TLS version
openssl s_client -connect mcp.example.com:443 -tls1_2
Issue: Certificate Expired
Symptom:
The remote certificate is invalid according to the validation procedure.
Solution:
- Check certificate expiry:
echo | openssl s_client -connect mcp.example.com:443 2>/dev/null | openssl x509 -noout -enddate - Renew certificate (see Certificate Management)
- Deploy new certificate
Issue: HSTS Not Working
Symptom: Browser still allows HTTP connections.
Solution:
- Verify HSTS header is sent:
curl -I https://mcp.example.com/health | grep Strict-Transport-Security - Clear browser HSTS cache (Chrome:
chrome://net-internals/#hsts) - Ensure middleware is configured correctly
Production Checklist
Before deploying to production, verify:
- Valid TLS certificate from trusted CA
- Certificate includes full chain (intermediate + root)
- Private key is stored securely (Kubernetes secret, Azure Key Vault, etc.)
- Certificate password is stored in environment variable (not in config)
- Certificate expiry monitoring is configured
- Automatic renewal is set up (Let's Encrypt, certbot)
- HSTS header is enabled
- HTTP → HTTPS redirect is configured
- TLS 1.2+ is enforced (no SSLv3, TLS 1.0, TLS 1.1)
- Strong cipher suites are configured
- Certificate Common Name (CN) matches domain
- Firewall allows port 443
- Health check endpoint is accessible via HTTPS
- Load balancer (if used) is configured for TLS passthrough or termination
- Monitoring/alerting is set up for certificate expiry
References
ASP.NET Core Documentation:
Security Best Practices:
Certificate Authorities:
- Let's Encrypt - Free automated certificates
- DigiCert - Commercial CA
- Sectigo - Commercial CA
Document Version: 1.0.0 Last Updated: 2025-10-19 Maintained By: Svrnty Development Team Related: Deployment Guide, Security Best Practices