
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/ollamaor 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.
- In Cloudflare Zero Trust, go to Access → Applications → Add an application.
- Choose Self-hosted.
- Application domain:
ollama.example.com. - Create an Allow policy. Start strict: allow only your email address or your organization’s email domain.
- (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.1and 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:11434only. - Nginx listens on
127.0.0.1:8080only (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_timeoutand avoid aggressive Cloudflare timeouts in your app settings. - Request size: Large prompts can exceed proxy defaults. Adjust
client_max_body_sizein Nginx if you hit413 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). Never0.0.0.0unless 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.