How to Securely Access Ollama and Swama Remotely on macOS with Caddy

Run two local AI runtimes behind a single secure reverse proxy with separate authentication

Running Ollama or Swama locally is straightforward: start the server and connect via localhost. But if you want to access your local AI from your iPhone or iPad while away from home, or allow trusted servers to connect, you need to add protection. Neither Ollama nor Swama has built-in authentication, which means anyone who discovers your open port could use your CPU and GPU for free.

In my setup, I run both Ollama (port 11434) and Swama (port 28100) on my Mac. I want secure access from my iPhone while traveling, plus access from a few trusted servers by IP or domain name. Each service gets its own Bearer token so I can revoke access independently if needed.

Below is a guide to setting up Caddy as a reverse proxy that enforces Bearer tokens and validates headers before forwarding requests to either backend.

What You’ll Need

  • macOS with Ollama and/or Swama running locally
  • Homebrew installed
  • Router access to forward external ports to your Mac
  • Bearer tokens for authentication (we’ll generate these)

Why Caddy?

  • Simple, readable config syntax
  • Built-in HTTPS when using a domain (optional)
  • Easy header checks and request filtering
  • Lightweight and fast

Architecture Overview

Internet → Router → Caddy (ports 8081/8082) → Ollama (11434) / Swama (28100)

We’ll expose:

  • Port 8081 for Ollama (with its own Bearer token)
  • Port 8082 for Swama (with its own Bearer token)

You can also use a single port with path-based routing if you prefer — I’ll show both options.


Step 1: Install Caddy

brew install caddy

Step 2: Generate Secure API Keys

Generate separate tokens for each service:

# Token for Ollama
echo "sk-ollama-$(openssl rand -hex 16)"
# Example output: sk-ollama-78834bcb4c76d97d35a0c1acd0d938c6
# Token for Swama
echo "sk-swama-$(openssl rand -hex 16)"
# Example output: sk-swama-a1b2c3d4e5f6789012345678abcdef01

Save these somewhere secure — you’ll need them for client apps.

Step 3: Create the Caddyfile

sudo mkdir -p /usr/local/etc/caddy

Option A: Separate Ports (Recommended)

This gives each service its own port and token:

cat > /usr/local/etc/caddy/Caddyfile << 'EOF'
{
auto_https off
admin off
}

# Ollama on port 8081
:8081 {
@validOllama expression `
{header.Authorization} == "Bearer sk-ollama-YOUR-OLLAMA-KEY-HERE" ||
{header.Referer}.contains("example.com") ||
{header.Origin}.contains("example.com") ||
{remote_host} == "YOUR.TRUSTED.IP.ADDRESS"
`
handle @validOllama {
reverse_proxy 127.0.0.1:11434
}
handle {
respond "Unauthorized" 401
}
}
# Swama on port 8082
:8082 {
@validSwama expression `
{header.Authorization} == "Bearer sk-swama-YOUR-SWAMA-KEY-HERE" ||
{header.Referer}.contains("example.com") ||
{header.Origin}.contains("example.com") ||
{remote_host} == "YOUR.TRUSTED.IP.ADDRESS"
`
handle @validSwama {
reverse_proxy 127.0.0.1:28100
}
handle {
respond "Unauthorized" 401
}
}
EOF

Option B: Single Port with Path-Based Routing

If you only want to forward one port on your router:

cat > /usr/local/etc/caddy/Caddyfile << 'EOF'
{
auto_https off
admin off
}

:8080 {
# Ollama routes
@ollamaAuth {
path /ollama/*
expression `{header.Authorization} == "Bearer sk-ollama-YOUR-OLLAMA-KEY-HERE"`
}
handle @ollamaAuth {
uri strip_prefix /ollama
reverse_proxy 127.0.0.1:11434
}
# Swama routes
@swamaAuth {
path /swama/*
expression `{header.Authorization} == "Bearer sk-swama-YOUR-SWAMA-KEY-HERE"`
}
handle @swamaAuth {
uri strip_prefix /swama
reverse_proxy 127.0.0.1:28100
}
handle {
respond "Unauthorized" 401
}
}
EOF

With this setup:

Step 4: Set Permissions

sudo chown root:wheel /usr/local/etc/caddy/Caddyfile
sudo chmod 644 /usr/local/etc/caddy/Caddyfile

Step 5: Create the Launch Service

Make Caddy start automatically with your Mac:

mkdir -p ~/Library/LaunchAgents

cat > ~/Library/LaunchAgents/homebrew.mxcl.caddy.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>homebrew.mxcl.caddy</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/caddy</string>
<string>run</string>
<string>--config</string>
<string>/usr/local/etc/caddy/Caddyfile</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/caddy-stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/caddy-stderr.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/Users/YOUR_USERNAME</string>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>
EOF

# Replace YOUR_USERNAME with your actual username
sed -i '' "s/YOUR_USERNAME/$USER/g" ~/Library/LaunchAgents/homebrew.mxcl.caddy.plist
# Load and start the service
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.caddy.plist
launchctl start homebrew.mxcl.caddy

Step 6: Configure Your Router

Forward external ports to your Mac:

  • External port 8081 → Mac’s internal IP:8081 (Ollama)
  • External port 8082 → Mac’s internal IP:8082 (Swama)

Or just port 8080 if using the single-port option.

Step 7: Verify It Works

# Check Caddy is running
launchctl list | grep caddy

# Test Ollama (should return model list)
curl http://localhost:8081/api/tags \
-H "Authorization: Bearer sk-ollama-YOUR-KEY"
# Test Swama (should return model list)
curl http://localhost:8082/v1/models \
-H "Authorization: Bearer sk-swama-YOUR-KEY"
# Test without token (should return 401)
curl http://localhost:8081/api/tags

Client App: Molten

Using this chance to promote my new app a little. Molten, now available on the App Store for Mac, iPhone, and iPad, is open source too — you can get it from our GitHub and compile it yourself if you prefer not to get it from the App Store.

Previously with Ollama, you could use Enchanted, which is a great client, but it doesn’t support Swama. (Ollama has its own API with some OpenAI API compatibility, but Swama is more strictly OpenAI API compatible.) So I hacked around with Enchanted’s code, got it working with Swama and Apple Foundation Model, and released it on the App Store and open-sourced the result.

Molten supports both Ollama and Swama — just specify the URLs and Bearer tokens for each, and it works. You can configure multiple backends and switch between them easily.

The plan is to make it into a private LLM and RAG app, so you can easily put a few documents into your own folders and it will turn them into a local knowledge base with no privacy concerns.


Quick Reference: API Endpoints

ServiceLocal URLExternal URL (with Caddy)Ollama Chathttp://localhost:11434/api/chathttp://your-ip:8081/api/chatOllama Modelshttp://localhost:11434/api/tagshttp://your-ip:8081/api/tagsSwama Chathttp://localhost:28100/v1/chat/completionshttp://your-ip:8082/v1/chat/completionsSwama Modelshttp://localhost:28100/v1/modelshttp://your-ip:8082/v1/models


Troubleshooting

Caddy won’t start:

# Check logs
cat /tmp/caddy-stderr.log

# Test config syntax
caddy validate --config /usr/local/etc/caddy/Caddyfile

Connection refused from outside:

  • Verify router port forwarding is correct
  • Check macOS firewall allows incoming connections on ports 8081/8082
  • Ensure your ISP doesn’t block these ports

401 Unauthorized when token is correct:

  • Make sure there’s no extra whitespace in your token
  • Check the Bearer prefix is exactly Bearer (with one space)

With this setup, you can securely access both your Ollama and Swama servers from anywhere — whether you’re on your iPhone with Molten, running Python scripts from a VPS, or building AI-powered apps that need a reliable backend.