***

title: Production Deployment
description: Deploy agents to production with SSL, authentication, monitoring, and scaling using uvicorn workers, nginx reverse proxy, and systemd.
slug: /guides/production
max-toc-depth: 3
---------------------

For a complete index of all SignalWire documentation pages, fetch https://signalwire.com/docs/llms.txt

[ref-agentbase]: /docs/server-sdks/reference/python/agents/agent-base

[ref-agentserver]: /docs/server-sdks/reference/python/agents/agent-server

[ref-datamap]: /docs/server-sdks/reference/python/agents/data-map

[ref-swmlservice]: /docs/server-sdks/reference/python/agents/swml-service

## Production checklist

### Security

* [ ] HTTPS enabled with valid certificates
* [ ] `SWML_BASIC_AUTH_PASSWORD` configured (username defaults to `signalwire`)
* [ ] Firewall rules in place
* [ ] No secrets in code or logs (SDK masks credentials in startup output automatically)

### Reliability

* [ ] Process manager (systemd/supervisor)
* [ ] Health checks configured
* [ ] Logging to persistent storage
* [ ] Error monitoring/alerting

### Performance

* [ ] Multiple workers for concurrency
* [ ] Reverse proxy (nginx) for SSL termination
* [ ] Load balancing if needed

## Environment variables

```bash
## Authentication (password required; username defaults to 'signalwire')
export SWML_BASIC_AUTH_PASSWORD="your-secure-password"
# export SWML_BASIC_AUTH_USER="signalwire"  # optional, defaults to 'signalwire'

## SSL Configuration
export SWML_SSL_ENABLED="true"
export SWML_SSL_CERT_PATH="/etc/ssl/certs/agent.crt"
export SWML_SSL_KEY_PATH="/etc/ssl/private/agent.key"

## Domain configuration
export SWML_DOMAIN="agent.example.com"

## Proxy URL (if behind load balancer/reverse proxy)
export SWML_PROXY_URL_BASE="https://agent.example.com"
# APP_URL is accepted as a fallback for SWML_PROXY_URL_BASE
```

## Running in production

Production deployment differs significantly by language. Each SDK provides its own HTTP server or integrates with language-specific production servers.

<Tabs>
  <Tab title="Python">
    Use uvicorn with multiple workers:

    ```bash
    ## Run with 4 workers
    uvicorn my_agent:app --host 0.0.0.0 --port 3000 --workers 4
    ```

    Create an entry point module:

    ```python
    #!/usr/bin/env python3
    # my_agent.py
    from signalwire import AgentBase

    class MyAgent(AgentBase):
        def __init__(self):
            super().__init__(name="my-agent")
            self.add_language("English", "en-US", "rime.spore")
            self.prompt_add_section("Role", "You are a helpful assistant.")

    if __name__ == "__main__":
        agent = MyAgent()
        agent.run(host="0.0.0.0", port=3000)
    ```
  </Tab>

  <Tab title="TypeScript">
    Use Node.js with clustering or a process manager like PM2:

    ```typescript
    // app.ts
    import { AgentBase } from 'signalwire-agents';

    const agent = new AgentBase({ name: 'my-agent' });
    agent.addLanguage({ name: 'English', code: 'en-US', voice: 'rime.spore' });
    agent.promptAddSection('Role', { body: 'You are a helpful assistant.' });

    agent.run({ host: '0.0.0.0', port: 3000 });
    ```

    ```bash
    ## Run with PM2 for process management
    npm install -g pm2
    pm2 start app.js -i 4 --name signalwire-agent
    ```
  </Tab>
</Tabs>

## Systemd service

Create `/etc/systemd/system/signalwire-agent.service`. Adjust `ExecStart` for your language:

| Language   | ExecStart Example                                                              |
| ---------- | ------------------------------------------------------------------------------ |
| Python     | `/opt/agent/venv/bin/uvicorn app:app --host 127.0.0.1 --port 3000 --workers 4` |
| TypeScript | `/usr/bin/node /opt/agent/app.js`                                              |

```ini
[Unit]
Description=SignalWire AI Agent
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/agent
Environment="SWML_BASIC_AUTH_USER=your-username"
Environment="SWML_BASIC_AUTH_PASSWORD=your-password"
ExecStart=/opt/agent/venv/bin/uvicorn app:app --host 127.0.0.1 --port 3000 --workers 4
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
```

Enable and start:

```bash
sudo systemctl enable signalwire-agent
sudo systemctl start signalwire-agent
sudo systemctl status signalwire-agent
```

## Nginx reverse proxy

```nginx
## /etc/nginx/sites-available/agent
server {
    listen 443 ssl http2;
    server_name agent.example.com;

    ssl_certificate /etc/ssl/certs/agent.crt;
    ssl_certificate_key /etc/ssl/private/agent.key;

    location / {
        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 $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;
    }
}

server {
    listen 80;
    server_name agent.example.com;
    return 301 https://$server_name$request_uri;
}
```

Enable the site:

```bash
sudo ln -s /etc/nginx/sites-available/agent /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```

## Production architecture

<Frame caption="Production Architecture">
  <img class="diagram" src="https://files.buildwithfern.com/signalwire.docs.buildwithfern.com/docs/7d62f511ce2a52d622e93193373cf9c0ff10c671383e25d729df01638ab6a4ca/assets/images/sdks/diagrams/07_02_production_diagram1.webp" alt="Production architecture diagram showing nginx, uvicorn workers, and SignalWire Cloud." />
</Frame>

## SSL configuration

### Using environment variables

```bash
export SWML_SSL_ENABLED="true"
export SWML_SSL_CERT_PATH="/path/to/cert.pem"
export SWML_SSL_KEY_PATH="/path/to/key.pem"
```

### Let's Encrypt with Certbot

```bash
## Install certbot
sudo apt install certbot python3-certbot-nginx

## Get certificate
sudo certbot --nginx -d agent.example.com

## Auto-renewal is configured automatically
```

## Health checks

For [AgentServer][ref-agentserver] deployments:

```bash
## Health check endpoint
curl https://agent.example.com/health
```

Response:

```json
{
    "status": "ok",
    "agents": 1,
    "routes": ["/"]
}
```

For load balancers, use this endpoint to verify agent availability.

## Logging configuration

```python
import logging

## Configure logging for production
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('/var/log/agent/agent.log'),
        logging.StreamHandler()
    ]
)
```

Or use environment variable:

```bash
export SIGNALWIRE_LOG_MODE=default
```

## Monitoring

### Prometheus metrics

Add custom metrics to your agent:

```python
from prometheus_client import Counter, Histogram, start_http_server

## Start metrics server on port 9090
start_http_server(9090)

## Define metrics
call_counter = Counter('agent_calls_total', 'Total calls handled')
call_duration = Histogram('agent_call_duration_seconds', 'Call duration')
```

### External monitoring

* **Uptime monitoring**: Monitor the health endpoint
* **Log aggregation**: Ship logs to ELK, Datadog, or similar
* **APM**: Use Application Performance Monitoring tools

## Scaling considerations

### Vertical scaling

| Language | Scaling Approach                         |
| -------- | ---------------------------------------- |
| Python   | Increase uvicorn workers (`--workers N`) |

\| TypeScript | Increase PM2 instances (`pm2 scale agent 8`) |

* Use larger server instances
* Optimize agent code and external calls

### Horizontal scaling

* Multiple server instances behind load balancer
* Stateless agent design
* Shared session storage (Redis) if needed

### Serverless

* Auto-scaling with Lambda/Cloud Functions
* Pay per invocation
* No server management

## Built-in security features

The SDK includes several security hardening features enabled by default.

### Security headers

All HTTP responses automatically include security headers:

| Header                      | Value                                 | Purpose                        |
| --------------------------- | ------------------------------------- | ------------------------------ |
| `X-Content-Type-Options`    | `nosniff`                             | Prevent MIME-type sniffing     |
| `X-Frame-Options`           | `DENY`                                | Prevent clickjacking           |
| `Referrer-Policy`           | `strict-origin-when-cross-origin`     | Limit referrer information     |
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` | Force HTTPS (when SSL enabled) |

No configuration is needed -- these headers are added automatically by middleware on [AgentBase][ref-agentbase], AgentServer, and [SWMLService][ref-swmlservice].

### SSRF protection

[DataMap][ref-datamap] HTTP requests, skill remote URLs, and MCP gateway URLs are validated against private IP ranges to prevent Server-Side Request Forgery attacks. By default, requests to internal networks (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`, and IPv6 equivalents) are blocked.

To allow private URLs (e.g., when your backend services are on a private network):

```bash
export SWML_ALLOW_PRIVATE_URLS=true
```

### Timing-safe authentication

Basic auth credential comparison uses `hmac.compare_digest()` to prevent timing side-channel attacks. SWAIG token validation also uses constant-time comparison.

### Credential masking

Startup log output shows `(credentials configured)` instead of the actual password:

```text
Agent 'my-agent' is available at:
URL: http://0.0.0.0:3000
Basic Auth: signalwire:(credentials configured) (source: environment)
```

### Default authentication username

`SWML_BASIC_AUTH_USER` defaults to `signalwire` when not explicitly set. You only need to configure `SWML_BASIC_AUTH_PASSWORD`:

```bash
export SWML_BASIC_AUTH_PASSWORD="your-secure-password"
# Username defaults to 'signalwire' — no need to set SWML_BASIC_AUTH_USER
```

### Proxy header validation

X-Forwarded headers (`X-Forwarded-Host`, `X-Forwarded-Proto`) are only trusted when explicitly configured. Set `SWML_TRUST_PROXY_HEADERS=true` if your agent runs behind a reverse proxy and you want the SDK to auto-detect the public URL from forwarded headers:

```bash
export SWML_TRUST_PROXY_HEADERS=true
```

When `SWML_PROXY_URL_BASE` is set via environment variable, proxy headers are automatically trusted for URL construction.

## Security best practices

**DO:**

* Use HTTPS everywhere
* Set strong basic auth credentials
* Use environment variables for secrets
* Enable firewall and limit access
* Regularly update dependencies
* Monitor for suspicious activity

**DON'T:**

* Expose debug endpoints in production
* Log sensitive data
* Use default credentials
* Disable SSL verification
* Run as root user