Back to tutorials
Tutorial

Secure Ollama API With Cloudflare Tunnel on VPS

Secure Ollama API on a VPS using Cloudflare Tunnel, TLS, and access controls—no inbound ports required.

By Anurag Singh
Updated on Oct 29, 2026
Category: Tutorial
Share article
Secure Ollama API With Cloudflare Tunnel on VPS

You can run Ollama on a VPS without turning it into a public endpoint. The goal is to secure Ollama API access without exposing port 11434 to the internet.

Cloudflare Tunnel handles networking through an outbound-only connection to Cloudflare’s edge. You then restrict access with Cloudflare Access, optional HTTP auth, and strict “localhost-only” listeners on the server.

This tutorial uses a practical setup: Ubuntu on a VPS, Ollama bound to localhost, a Cloudflare Tunnel publishing a private hostname, and Access policies that only allow you (or your team).

You’ll end up with a URL you can use from Open WebUI, Continue.dev, custom apps, or curl. You still won’t open inbound ports for Ollama.

What you’ll build (and what you won’t)

  • Private remote access to Ollama’s HTTP API via a Cloudflare-managed hostname.
  • No open inbound ports for Ollama (and typically none for the tunnel either).
  • TLS end-to-end: browser/client to Cloudflare, and Cloudflare to your VPS.
  • Identity-based access using Cloudflare Access (recommended) plus optional extra auth at Nginx.

You are not building a public LLM endpoint.

If you plan to expose an API to the open internet, you’ll need serious rate limiting, abuse controls, and clear policy/legal guardrails.

Prerequisites and resource sizing for Ollama on a VPS

Cloudflare Tunnel has minimal resource impact. Your model choice determines the VPS size.

  • OS: Ubuntu 22.04/24.04 LTS (commands below assume Ubuntu).
  • CPU-only Ollama: fine for small models and light concurrency. Expect slower responses and more CPU time per token.
  • RAM: budget roughly model size + overhead. Quantized models help, but you still need headroom for the OS and page cache.
  • Disk: leave room for model files (often several GB each) under /usr/share/ollama or your configured model directory.

If you’re picking infrastructure, start with a HostMyCode VPS for CPU-first testing.

If you already need steady throughput for multiple users, size up early (or move to dedicated compute). That helps you avoid the “works in dev, stalls in prod” wall.

Step 1: Install and verify Ollama (local-only)

If Ollama is already installed, skip ahead to the binding checks. Otherwise:

sudo apt update
sudo apt -y install curl ca-certificates

Install Ollama using the upstream installer:

curl -fsSL https://ollama.com/install.sh | sh

Confirm the service is running:

sudo systemctl status ollama --no-pager

Pull a small test model (use whatever you normally run). Example:

ollama pull llama3

Confirm the API answers locally:

curl -s http://127.0.0.1:11434/api/tags | head

If you want Ollama managed cleanly at boot with systemd, see: Ollama Systemd Service on Ubuntu VPS.

Step 2: Bind Ollama to localhost (do not expose it)

If you take only one thing from this guide, take this: keep Ollama listening on 127.0.0.1 (or use a Unix socket).

Most accidental exposures come from binding to 0.0.0.0.

Check the listening address:

sudo ss -lntp | grep 11434 || true

You want 127.0.0.1:11434 (or [::1]), not 0.0.0.0:11434.

If it’s listening on all interfaces, force loopback with a systemd override:

sudo systemctl edit ollama

Paste this override (only adjust if you already manage environment variables):

[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"

Reload and restart:

sudo systemctl daemon-reload
sudo systemctl restart ollama

Re-check the listener:

sudo ss -lntp | grep 11434

Step 3: Put Nginx in front (optional, but recommended for extra control)

Cloudflare Tunnel can connect directly to http://127.0.0.1:11434. Nginx adds a clean control point in front of Ollama.

With Nginx, you can set request size limits, add optional extra auth, and keep straightforward access logs. You also avoid changing Ollama’s process for most tweaks.

Install Nginx:

sudo apt -y install nginx apache2-utils

Create an internal-only Nginx server block on localhost (no public listener):

sudo tee /etc/nginx/sites-available/ollama-internal > /dev/null <<'EOF'
server {
  listen 127.0.0.1:8080;
  server_name _;

  # Basic hardening
  client_max_body_size 20m;
  proxy_read_timeout 300s;

  location / {
    proxy_pass http://127.0.0.1:11434;
    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;
  }
}
EOF

Enable the site and validate config:

sudo ln -s /etc/nginx/sites-available/ollama-internal /etc/nginx/sites-enabled/ollama-internal
sudo nginx -t
sudo systemctl reload nginx

Test locally:

curl -s http://127.0.0.1:8080/api/tags | head

If Nginx returns 502, treat it as a local upstream problem first.

In practice, Ollama usually isn’t listening where you think it is.

This diagnostic write-up helps: Nginx 502 Bad Gateway Fix on Ubuntu VPS.

Step 4: Create a Cloudflare Tunnel (no open inbound ports)

This section assumes your domain is already in Cloudflare. If it isn’t, move DNS to Cloudflare first.

You can register domains via HostMyCode domains.

Install cloudflared on Ubuntu. Cloudflare’s package steps can change, so follow Cloudflare’s official Ubuntu install method.

After install, confirm:

cloudflared --version

Authenticate your server to your Cloudflare account:

cloudflared tunnel login

This prints a URL. Open it on your workstation, log in, and select the correct zone.

Cloudflared will store a cert under your user’s home directory (commonly ~/.cloudflared).

Create a named tunnel:

cloudflared tunnel create ollama-private

You’ll get a Tunnel UUID. Next, create a DNS record for the hostname you’ll use, for example ollama.example.com:

cloudflared tunnel route dns ollama-private ollama.example.com

Step 5: Configure the tunnel to reach your local proxy (or Ollama)

Create the config directory and file:

mkdir -p ~/.cloudflared

Create ~/.cloudflared/config.yml.

If you used the Nginx local proxy on port 8080, point the tunnel at that:

tee ~/.cloudflared/config.yml > /dev/null <<'EOF'
tunnel: ollama-private
credentials-file: /home/<YOUR_USER>/.cloudflared/<TUNNEL-UUID>.json

ingress:
  - hostname: ollama.example.com
    service: http://127.0.0.1:8080
  - service: http_status:404
EOF

Replace <YOUR_USER> and <TUNNEL-UUID>.

If you skipped Nginx, use service: http://127.0.0.1:11434 instead.

Run it once in the foreground to validate routing:

cloudflared tunnel --config ~/.cloudflared/config.yml run

From your laptop, try:

curl -i https://ollama.example.com/api/tags

It may already respond, but don’t treat that as “done.”

Until Access is in place, the hostname is not tied to your identity.

Step 6: Lock it down with Cloudflare Access (identity-based auth)

Cloudflare Access is the control plane here.

You can require Google Workspace, Microsoft Entra ID, GitHub, one-time PIN, or your preferred identity provider.

  1. In Cloudflare Zero Trust, go to Access → Applications → Add an application.
  2. Choose Self-hosted.
  3. Application domain: ollama.example.com.
  4. Create an Allow policy. Start strict: allow only your email address or your organization’s email domain.
  5. (Optional) Add device posture rules if you use managed devices.

Re-test your URL. Without a valid identity, requests should be blocked (or redirected into the Access login flow).

With a valid identity, you should reach the upstream service.

Step 7: Enforce TLS “edge to origin” inside the tunnel

The tunnel runs outbound, which helps. You should still think about the hop between cloudflared and your local service.

You have two practical options:

  • Keep origin HTTP on localhost (what we did). This is reasonable if the only listener is 127.0.0.1 and the server isn’t shared with untrusted users.
  • Run Nginx with a local TLS listener (127.0.0.1:8443) and have cloudflared connect via https://127.0.0.1:8443. This adds setup overhead, but you get cryptographic protection on the hop as well.

Most single-purpose VPS setups stick with localhost HTTP and rely on OS boundaries.

If you run multiple untrusted services/users on the same machine, use a local TLS listener or a Unix socket.

Step 8: Add a second layer of auth at Nginx (optional but useful)

Cloudflare Access should be your primary gate.

Basic auth at Nginx works well as a backstop if an Access policy is loosened by mistake.

It can also help with simple server-to-server calls.

Create an htpasswd file:

sudo htpasswd -c /etc/nginx/.htpasswd-ollama ollamauser

Edit the Nginx site and add auth in location /:

sudo nano /etc/nginx/sites-available/ollama-internal
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd-ollama;

Reload Nginx:

sudo nginx -t && sudo systemctl reload nginx

Test locally:

curl -i http://127.0.0.1:8080/api/tags
curl -u ollamauser 'http://127.0.0.1:8080/api/tags'

If you’d rather do basic auth directly in front of Ollama without Cloudflare, HostMyCode also has a guide: Run Ollama Behind Nginx With Basic Auth.

Here you’re stacking controls: Access plus optional basic auth.

Step 9: Run cloudflared as a systemd service

Don’t keep tunnels alive in a screen or tmux session. Use a systemd unit instead.

This ensures the tunnel starts on boot and restarts after failures.

Create a dedicated user:

sudo useradd --system --home /var/lib/cloudflared --shell /usr/sbin/nologin cloudflared
sudo mkdir -p /var/lib/cloudflared/.cloudflared
sudo chown -R cloudflared:cloudflared /var/lib/cloudflared

Move your tunnel credentials/config into that home (adjust paths as needed):

sudo cp -a ~/.cloudflared/* /var/lib/cloudflared/.cloudflared/
sudo chown -R cloudflared:cloudflared /var/lib/cloudflared/.cloudflared

Edit the config file to point to the new credentials path, for example:

sudo nano /var/lib/cloudflared/.cloudflared/config.yml

Now create a systemd unit:

sudo tee /etc/systemd/system/cloudflared-ollama.service > /dev/null <<'EOF'
[Unit]
Description=Cloudflare Tunnel for private Ollama API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=cloudflared
ExecStart=/usr/bin/cloudflared tunnel --config /var/lib/cloudflared/.cloudflared/config.yml run
Restart=on-failure
RestartSec=5s

# Basic sandboxing
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/cloudflared

[Install]
WantedBy=multi-user.target
EOF

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared-ollama.service
sudo systemctl status cloudflared-ollama.service --no-pager

Tail logs if needed:

journalctl -u cloudflared-ollama.service -f

Step 10: Firewall and exposure checks (quick diagnostics)

Assume you’ll misclick something in Cloudflare one day.

Your VPS should still refuse direct access to Ollama.

  • Ollama listens on 127.0.0.1:11434 only.
  • Nginx listens on 127.0.0.1:8080 only (if you used it).
  • Your VPS firewall allows only what you actually need (typically SSH, and maybe HTTP/HTTPS for other sites).

On Ubuntu with UFW, a typical baseline is:

sudo ufw status verbose
sudo ufw allow OpenSSH
sudo ufw enable

If you host websites on the same VPS, you’ll also allow 80/443.

If this is an AI-only box and you SSH through a VPN, you can go tighter.

For a clean UFW setup guide, use: Set Up UFW Firewall on Ubuntu for a Web Server.

Finally, run an external port scan (from another machine). Confirm 11434 is not reachable.

Using your private Ollama endpoint from apps

Once Access is working, point your tools at https://ollama.example.com.

In practice, usage tends to fall into two buckets:

  • Human interactive use: log into Access in a browser, then use Open WebUI or a web client that can reuse the Access session.
  • Service-to-service use: use a dedicated Access policy with a service token, or lock it down with mTLS/cert pinning if you have a strong requirement. (Exact steps depend on your Cloudflare Zero Trust setup.)

If you’re heading toward a chat UI, HostMyCode has a walkthrough for hosting the frontend separately while still using Ollama as the backend: Self-Host Open WebUI With Ollama on a VPS.

Operational limits and gotchas

  • Streaming responses: Some clients stream tokens over a long-lived HTTP connection. If you see disconnects, increase Nginx proxy_read_timeout and avoid aggressive Cloudflare timeouts in your app settings.
  • Request size: Large prompts can exceed proxy defaults. Adjust client_max_body_size in Nginx if you hit 413 Request Entity Too Large.
  • Concurrency: CPU-only inference handles limited parallel chats. If two people generate long answers at once, latency climbs quickly.
  • Logs: Cloudflare Access logs tell you who authenticated; Nginx access logs help you debug client behavior and request patterns.

Hardening checklist for a self-hosted AI endpoint

  • Bind Ollama to 127.0.0.1 (or a Unix socket). Never 0.0.0.0 unless you truly mean public.
  • Use Cloudflare Access allow policies (email/domain). Start strict, loosen later.
  • Add a second auth layer (optional) at Nginx for defense in depth.
  • Keep automatic security updates enabled on the VPS.
  • Monitor CPU/RAM/disk. Sudden disk growth often means “too many models pulled.”

For patching hygiene on Ubuntu servers, you can follow: Set Up Automated Security Updates on Ubuntu VPS.

Summary: secure remote LLM access without public ports

You now have a repeatable way to secure Ollama API access on a VPS.

Keep Ollama local, publish it through a Cloudflare Tunnel, and enforce identity-based access controls.

This pattern cuts attack surface and avoids brittle “please allowlist my IP” routines.

If you want a predictable environment for always-on AI services, run this on a dedicated instance instead of a crowded multi-purpose server.

A managed VPS hosting plan from HostMyCode can also cover routine patching and baseline hardening so you can focus on the application layer.

If you’re building private AI tools for your team, start with a VPS you control end-to-end. HostMyCode offers HostMyCode VPS plans for self-managed operators and managed VPS hosting if you want help keeping the base server secure and stable.

FAQ

Do I still need to open ports 80/443 on my VPS?

Not for the tunnel itself. Cloudflare Tunnel is outbound-only.

If the VPS also hosts websites, you may still open 80/443 for those sites, but the Ollama API can stay local-only.

Should I point the tunnel directly at Ollama or at Nginx?

Pointing at Nginx is usually cleaner. You get request limits, better logs, and an extra auth layer without touching Ollama’s process.

Can I use this with Open WebUI or a coding assistant?

Yes, as long as the client can authenticate through Cloudflare Access (interactive login) or you set up a service-to-service method supported by your Access policies.

If you’re self-hosting tools, start with the Open WebUI guide linked above.

What’s the biggest performance bottleneck on a CPU-only VPS?

Token generation speed. Small quantized models can be usable for personal workflows, but multi-user chat on CPU tends to feel slow.

If latency matters, move to a larger instance or GPU-backed infrastructure.

How do I confirm I didn’t accidentally expose Ollama publicly?

Run ss -lntp | grep 11434 and confirm it only listens on 127.0.0.1.

Then scan your server from outside and ensure port 11434 isn’t reachable.