Local LLM Security – Dubai – Self-Hosted AI – 2026

How to Secure a Self-Hosted Ollama API in Dubai with Cloudflare Tunnel and API-Key Authentication

The Orange Club AI Integration Practice 14 min read

TL;DR

Running Ollama locally is easy. The problem is what happens next: you expose it to the internet and discover Ollama ships with zero built-in authentication. For Dubai businesses running local LLMs for document processing, email classification, or data-sovereign AI workflows, this gap needs closing before anything goes near a public domain. This guide shows how: a lightweight FastAPI proxy validates an API key before any request reaches Ollama – no Cloudflare Access subscription, no VPS, no open ports, no changes to Ollama itself. The full chain: Browser → Cloudflare Tunnel → FastAPI proxy (validates key) → Ollama. An unauthorized request never reaches the model.

Securing a self-hosted Ollama API in Dubai with Cloudflare Tunnel is the step most local LLM guides skip entirely. They cover installation, model pulling, and basic API calls – then stop at exactly the point where the real operational challenge begins. If you have a local model running on UAE infrastructure and need to reach it remotely – whether for internal automation, a Dubai agentic AI workflow, or a team-facing chat interface – this is the guide that closes the gap between “it works on localhost” and “it is safe to expose.”

The starting point for most self-hosted Ollama setups in Dubai is the same: Ollama is running on a Windows or Linux machine – on-premise at the office, at a home office, or on a local server inside a UAE data centre – it works perfectly over localhost, and at some point there is a legitimate need to access it from somewhere else. Another device. A browser-based frontend. An automation workflow that runs from a different machine. So a tunnel gets created, a domain gets pointed at port 11434, and everything works.

Then the logs appear.

Ollama’s GIN logs start showing public IP addresses hitting /api/tags and /api/generate. Those are not your requests. /api/tags returns your installed model list. /api/generate invokes inference. Anyone who can reach your domain can use your hardware, consume your resources, and query your models – without a single credential. Automated scanning tools find open inference endpoints within hours of them going live. For a Dubai business running sensitive workflows – invoice processing, client document extraction, internal knowledge queries – that exposure is not acceptable.

This is not a hypothetical. It happened to us. The fix took under two hours to build and has been running in production on UAE infrastructure since. Here is exactly how it works.


Why Ollama Has No Built-In Authentication – and Why That Matters in Dubai

Ollama is designed as a local inference server. Its entire architecture assumes it is running on a trusted network – typically just localhost. Authentication was not in scope for the original design, and that is a reasonable trade-off for a tool built to run locally. The problem is that as soon as you put it behind a public domain – even through a tunnel – that localhost assumption no longer holds.

For Dubai businesses, the stakes are higher than just compute costs. Clients processing financial documents, legal records, or any data covered by the Federal Decree-Law No. 45 of 2021 (UAE Personal Data Protection Law – PDPL) are sending that material through the inference pipeline. An open Ollama endpoint means that data is accessible to anyone who finds the URL. The PDPL places the obligation to protect personal data on the data controller – the business running the model. “The tunnel was open by default” is not a defence.

The three obvious solutions each have a problem:

OptionThe Problem
Cloudflare Access (Service Token)Requires payment details during Zero Trust onboarding, even for free-tier usage
Nginx reverse proxy with basic authRequires a VPS or server with Nginx installed and maintained
Direct port forwarding with firewall rulesExposes a port directly to the internet and requires router-level access and ongoing rule management

The solution that avoids all three problems: build a tiny authentication proxy that sits between Cloudflare and Ollama, runs entirely on your local machine, and requires nothing beyond Python and three packages.


The Architecture Before and After

The insecure architecture that most tunneled Ollama setups start with looks like this:

Before – No Authentication

Internet ↓ (any request, no credentials required) api.yourdomain.com ↓ Cloudflare Tunnel Ollama :11434← directly reachable, no gate

The secured architecture after this guide:

After – API-Key Authentication via FastAPI Proxy

Internet ↓ Authorization: Bearer <api-key> required api.yourdomain.com ↓ Cloudflare Tunnel FastAPI Proxy :8080← validates key, rejects or forwards ↓ only on valid key Ollama :11434← never directly reachable from network

Cloudflare knows nothing about Ollama. It only knows about the proxy on port 8080. Ollama remains bound to 127.0.0.1 and is unreachable from the network at all – only the proxy accepts connections, and only through the tunnel. An unauthenticated request gets a 401 response from the proxy and stops there.


Step-by-Step: Building the Authentication Proxy

Step 1 – Install the proxy dependencies

You need three Python packages. Run this on the machine where Ollama is running:

Install Dependencies

python -m pip install fastapi uvicorn httpx

FastAPI handles the HTTP layer. Uvicorn is the ASGI server that runs it. HTTPX is the async HTTP client used to forward authenticated requests to Ollama.

Step 2 – Create the proxy script

Create a file at a path you will remember – for example C:\ai\ollama_proxy.py – and paste this:

ollama_proxy.py – Complete Proxy

import os
import httpx
from fastapi import FastAPI, Request, Response

app = FastAPI()
API_KEY = os.environ["OLLAMA_API_KEY"]
OLLAMA_BASE = "http://127.0.0.1:11434"

@app.api_route("/{path:path}", methods=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"])
async def proxy(request: Request, path: str):
    auth_header = request.headers.get("Authorization", "")
    provided_key = auth_header.removeprefix("Bearer ").strip()
    if provided_key != API_KEY:
        return Response(
            content='{"error":"Unauthorized"}',
            status_code=401,
            media_type="application/json"
        )
    async with httpx.AsyncClient(timeout=120.0) as client:
        body = await request.body()
        upstream = await client.request(
            method=request.method,
            url=f"{OLLAMA_BASE}/{path}",
            headers={k: v for k, v in request.headers.items()
                     if k.lower() not in ("host", "authorization")},
            content=body,
            params=dict(request.query_params)
        )
        return Response(
            content=upstream.content,
            status_code=upstream.status_code,
            headers=dict(upstream.headers),
            media_type=upstream.headers.get("content-type")
        )

This proxy does exactly one thing: extract the bearer token from the Authorization header and check it against the environment variable. If the key matches, the request is forwarded to Ollama with the Authorization header stripped (Ollama does not need to see it). If the key is missing or wrong, the request ends at the proxy with a 401. Ollama is never touched.

Why environment variables, not a config file

The key is read from os.environ["OLLAMA_API_KEY"], not from a config file or hardcoded into the script. This is the correct pattern: a config file can be accidentally committed to source control, shared, or left readable by other users on the same machine. An environment variable exists only in the process context and is not visible in the source code. If you ever share this script with another team member or push it to a repository, the key is not in it.

Step 3 – Set the API key as an environment variable

Before starting the proxy, set the environment variable in the same terminal session:

Windows PowerShell

$env:OLLAMA_API_KEY="your-key-here-make-it-long-and-random"

Linux / macOS

export OLLAMA_API_KEY="your-key-here-make-it-long-and-random"

Use a key that is at least 32 characters. A UUID works well – you can generate one with python -c "import uuid; print(uuid.uuid4())". This value becomes the credential every client must present.

Step 4 – Start the proxy

Start the FastAPI Proxy

python -m uvicorn ollama_proxy:app --host 127.0.0.1 --port 8080

The --host 127.0.0.1 flag is important. It binds the proxy only to the loopback interface – it is not reachable from your local network, only from the machine itself and through the Cloudflare Tunnel. If you bind to 0.0.0.0, the proxy is reachable from your LAN without authentication, which defeats part of the purpose.

Step 5 – Verify authentication locally before touching the tunnel

Test the proxy before routing tunnel traffic to it. From the same machine, run both of these:

Test Without a Key – Should Fail

curl http://127.0.0.1:8080/api/tags
# Expected: {"error":"Unauthorized"}

Test With a Valid Key – Should Succeed

curl http://127.0.0.1:8080/api/tags \
  -H "Authorization: Bearer your-key-here-make-it-long-and-random"
# Expected: {"models":[...]}

If the first returns a model list, the proxy is not running or the port is wrong. Fix that before proceeding. If both return the model list, something is misconfigured in the key comparison – check that the environment variable is set in the same terminal session where you started uvicorn.

Step 6 – Update the Cloudflare Tunnel ingress

Open your Cloudflare Tunnel configuration file. The default location on Windows is C:\Users\USERNAME\.cloudflared\config.yml.

Change the service target from Ollama’s port to the proxy’s port:

config.yml – Before

tunnel: YOUR_TUNNEL_ID
credentials-file: C:\Users\USERNAME\.cloudflared\YOUR_TUNNEL_ID.json

ingress:
  - hostname: api.yourdomain.com
    service: http://localhost:11434   # direct to Ollama - INSECURE
  - service: http_status:404

config.yml – After

tunnel: YOUR_TUNNEL_ID
credentials-file: C:\Users\USERNAME\.cloudflared\YOUR_TUNNEL_ID.json

ingress:
  - hostname: api.yourdomain.com
    service: http://localhost:8080    # FastAPI proxy - auth enforced
  - service: http_status:404

Restart the tunnel for the change to take effect. On Windows, restart the Cloudflare Tunnel Windows service, or stop and restart the cloudflared process if you are running it manually.

Step 7 – Verify the full chain end-to-end

Now test from a different machine or from your phone on a mobile network – anything that routes through the public internet rather than your LAN:

Public Access Without Key – Should Fail

curl https://api.yourdomain.com/api/tags
# Expected: {"error":"Unauthorized"}

Public Access With Valid Key – Should Succeed

curl https://api.yourdomain.com/api/tags \
  -H "Authorization: Bearer your-key-here-make-it-long-and-random"
# Expected: {"models":[...]}

The full chain is now: Internet request arrives at Cloudflare – Cloudflare forwards it to your tunnel – the tunnel delivers it to the FastAPI proxy on port 8080 – the proxy validates the key – on success, it forwards to Ollama on port 11434 – the response travels back the same way. Ollama never saw an unauthenticated request.


The Architecture Comparison

PropertyBefore (direct tunnel)After (FastAPI proxy)
Authentication requiredNoneBearer token on every request
Ollama port exposureReachable via tunnel127.0.0.1 only, not reachable from network
Cloudflare knowledge of OllamaKnows the port directlyKnows only the proxy
External auth dependencyNone needed (but none present)None – key validated locally
Payment card requiredNoNo
Changes to Ollama requiredNoneNone
VPS or Nginx requiredNoNo
Key rotation processN/AUpdate environment variable, restart proxy

Securing the Frontend

If you have a browser-based frontend that calls the Ollama API, the proxy solves the backend but leaves an open question: where does the key go? The wrong answer is to hardcode it on the server side or embed it in the frontend JavaScript bundle where any user can read it from the source.

The approach that works for internal tools: the key lives in the user’s browser and travels with their requests. The server never holds it.

The Nginx configuration (on your VPS or reverse proxy)

If you have an Nginx instance serving your frontend at chat.yourdomain.com, update the /api/ location block to pass the Authorization header through rather than injecting a key server-side:

nginx.conf – Correct Header Passthrough

location /api/ {
    proxy_pass https://api.yourdomain.com;
    proxy_set_header Host api.yourdomain.com;
    proxy_set_header Authorization $http_authorization;  # forward from browser
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_ssl_server_name on;
}

The key difference is $http_authorization – this is the value the browser sent, not a value stored on the server. The VPS does not know the key. It forwards whatever the browser presented, and the FastAPI proxy on the other end validates it.

The frontend key management

The frontend needs three behaviors: show a key entry screen before the chat loads, validate the key against the live API, and store it for the session. Here is the JavaScript that handles this:

Frontend – Key Validation and Storage

async function validateAndStoreKey(key) {
  const res = await fetch("/api/tags", {
    headers: { "Authorization": `Bearer ${key}` }
  });
  if (res.ok) {
    localStorage.setItem("ollama_api_key", key);
    showChat();                        // key is valid - load the chat UI
  } else {
    showError("Invalid key. Check your credentials and try again.");
  }
}

function getAuthHeader() {
  const key = localStorage.getItem("ollama_api_key") || "";
  return `Bearer ${key}`;
}

// Every API call sends the stored key as a bearer token
async function generate(prompt) {
  const res = await fetch("/api/generate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": getAuthHeader()
    },
    body: JSON.stringify({ model: "qwen2.5:1.5b", prompt, stream: false })
  });
  if (res.status === 401) {
    localStorage.removeItem("ollama_api_key");
    showKeyScreen();                   // key expired or rotated - re-prompt
    return;
  }
  return res.json();
}

The result is that the key lives in exactly two places: the environment variable on the machine running the proxy, and the user’s own localStorage. It never sits on the VPS. It never appears in server logs. Someone opening chat.yourdomain.com without the key sees only the key prompt and cannot reach the API at any point in the chain.

When localStorage is not enough

localStorage is appropriate for internal tools with a small, trusted user set. For any externally facing deployment, replace it with a short-lived session token issued by a backend service that holds the key server-side and never exposes it to the browser. The proxy architecture does not change – only the key delivery mechanism does.


The Complete Data Flow After This Guide

Secured Ollama API data flow diagram Browser localStorage key → Authorization header HTTPS Cloudflare api.yourdomain.com Tunnel routing Tunnel FastAPI Proxy :8080 | validates bearer token valid key Ollama :11434 127.0.0.1 UNAUTHORIZED REQUEST PATH Unknown Client no auth token or wrong key Cloudflare passes through to tunnel Proxy Rejects 401 Unauthorized Request ends here Ollama never reached Key properties of this architecture – Cloudflare Tunnel has no knowledge of Ollama’s port – only the proxy on :8080 – Ollama is bound to 127.0.0.1 and cannot receive network connections directly – The bearer token is validated by the proxy only – never exposed in config files or source code – No VPS, no Nginx, no Docker, no Cloudflare Access subscription required

The complete request flow after this guide. Green paths show authenticated traffic. Red dashed paths show unauthenticated requests – they reach the proxy and stop there. Ollama on :11434 is never reachable from the network.


Operational Details That Matter

Key rotation

Rotate the key if it is ever exposed – shared accidentally, visible in a browser console, or included in a bug report. The rotation process is:

1
Generate a new key

Run python -c "import uuid; print(uuid.uuid4())" to generate a fresh UUID, or use any random string generator that produces at least 32 characters.

2
Update the environment variable

Set OLLAMA_API_KEY to the new value in the terminal session where the proxy will run.

3
Restart the proxy

Stop the uvicorn process and restart it. The proxy reads the environment variable at startup – there is no live reload.

4
Update clients

Anyone using the API – including your own frontend – will get 401 responses until they update their stored key. On the frontend with localStorage, this triggers the key-entry screen automatically.

Running the proxy as a persistent service on Windows

A proxy that stops when you close the terminal is not production-ready. Register it as a Windows service using NSSM (Non-Sucking Service Manager), available at nssm.cc:

Register as a Windows Service

# Run in an elevated PowerShell terminal
nssm install OllamaProxy "python" "-m uvicorn ollama_proxy:app --host 127.0.0.1 --port 8080"
nssm set OllamaProxy AppDirectory "C:\ai"
nssm set OllamaProxy AppEnvironmentExtra "OLLAMA_API_KEY=your-key-here"
nssm start OllamaProxy

NSSM handles restart on failure, logging, and service start on boot. The proxy now survives reboots and does not require a terminal window to stay open.

On Linux, use a systemd unit

/etc/systemd/system/ollama-proxy.service

[Unit]
Description=Ollama API Authentication Proxy
After=network.target

[Service]
User=your-user
WorkingDirectory=/home/your-user/ai
Environment=OLLAMA_API_KEY=your-key-here
ExecStart=python -m uvicorn ollama_proxy:app --host 127.0.0.1 --port 8080
Restart=always

[Install]
WantedBy=multi-user.target

Enable and Start

sudo systemctl daemon-reload
sudo systemctl enable ollama-proxy
sudo systemctl start ollama-proxy

What This Architecture Does Not Cover

Single-key authentication is the right starting point for internal tools, personal deployments, and small team access. It is not a complete security posture. Four gaps are worth knowing before you ship this to production – two are easy to close, two require a deliberate decision.

Gap 1 – Rate Limiting

The proxy as written forwards every authenticated request to Ollama without limit. A client holding the valid key can flood the API – intentionally or by running a tight loop. The fix is one package: add slowapi to the proxy and decorate the route with a per-IP or per-key limit. For most Dubai internal deployments with a known user set, this is low priority. For any deployment where the key is shared more broadly, close this gap before go-live.

Gap 2 – Key Logging in Transit

If Nginx or any intermediate proxy on your stack logs full request headers, the Authorization header value will appear in those logs. That means your bearer token is sitting in a log file somewhere. Review your Nginx access_log configuration and either disable header logging or explicitly exclude the Authorization field. This takes under five minutes to fix and should be done before the first external request goes through.

Gap 3 – Multi-User Isolation

All authenticated clients share the same Ollama instance. One client running a long document extraction job blocks others while Ollama processes synchronously. For a single-user or two-person internal tool this is not a problem. For a shared deployment across a Dubai team with concurrent usage, consider a lightweight request queue in front of the proxy – or a second Ollama instance for heavy workloads – before complaints about timeouts start arriving.

Gap 4 – Prompt Data Visibility to Cloudflare

Cloudflare Tunnel encrypts traffic in transit between the public internet and your tunnel endpoint. However, Cloudflare is the TLS termination point – which means the content of every prompt sent to Ollama passes through Cloudflare’s infrastructure in plaintext at that layer. For most business automation workflows this is acceptable. For highly sensitive data – patient records in healthcare workflows governed by the DHA Health Data Protection and Confidentiality Policy, financial data covered by CBUAE or DFSA requirements, or any personal data processed under Federal Decree-Law No. 45 of 2021 (UAE Personal Data Protection Law – PDPL) – evaluate whether a zero-knowledge transit path is required. The alternative is a WireGuard or OpenVPN tunnel directly to the machine, which bypasses Cloudflare entirely and keeps prompt data inside your controlled network.


The Practical Security Baseline for Dubai Local LLM Deployments

A self-hosted Ollama instance without authentication is a free inference endpoint for anyone who finds your domain. Automated scanners do not care that your server is running in Dubai – they probe public IPs globally and log every open port they find. The FastAPI proxy described here closes that gap with a single Python file, three pip packages, and one configuration change to your Cloudflare Tunnel. The authentication layer is completely independent of Ollama, completely independent of Cloudflare, and entirely under your control.

For Dubai businesses running local LLMs for data-sovereign AI workflows – invoice processing, email triage, document extraction, internal knowledge queries – this architecture gives you a secure foundation that satisfies both the operational requirement (remote access to the model) and the compliance requirement (data stays on UAE infrastructure, access is controlled). The step from this foundation to a production AI integration – connecting the model to your actual business systems, building the document pipelines, handling Arabic-English workflows – is where The Orange Club’s AI integration practice picks up.

If you are still in the model selection stage – evaluating which Ollama models actually perform on Dubai B2B tasks before committing to infrastructure – see our Llama vs Qwen Dubai comparison, which covers exactly that ground with real output data and prompt engineering results.

Frequently Asked Questions: Securing Ollama in Dubai with Cloudflare Tunnel

Does Ollama have built-in API authentication?

No. Ollama does not ship with built-in API key authentication. If you expose it directly through a tunnel or port forward – which many Dubai businesses do when setting up local LLM infrastructure for data-sovereign AI workflows – any client that can reach your domain can invoke model inference and retrieve your installed model list without any credentials. The authentication layer must be added separately: either through a reverse proxy, a dedicated auth service, or the lightweight FastAPI proxy this guide covers, which validates a bearer token before forwarding any request.

Why use FastAPI instead of Cloudflare Access for Ollama authentication?

Cloudflare Access with a Service Token is a legitimate option, but it requires payment details during Zero Trust onboarding even for low-usage deployments. The FastAPI proxy approach avoids any external authentication dependency: the key lives in an environment variable on your local machine, validation happens before the request reaches Ollama, and the entire auth layer is a single Python file you control. There are no third-party accounts involved in the authentication decision.

Is it safe to store the Ollama API key in browser localStorage?

localStorage is readable by any JavaScript running on the same origin, which makes it unsuitable for high-sensitivity credentials. For an internal Dubai business tool with a small, trusted user set, it is a practical tradeoff: the key never sits on the VPS, it is not stored in server-side sessions, and it is only as exposed as the user’s own browser. For higher-security requirements – or any deployment where the frontend is externally accessible – replace localStorage with short-lived session tokens issued by a backend that holds the master key server-side and never exposes it to the browser. The proxy architecture stays the same either way; only the key delivery mechanism changes.

What happens to requests that reach Ollama without authentication under this architecture?

Under this architecture, it is not possible for an unauthenticated request to reach Ollama. The Cloudflare Tunnel routes all traffic to the FastAPI proxy, not directly to Ollama. The proxy rejects any request missing a valid bearer token with a 401 error before forwarding anything downstream. Ollama itself remains bound to 127.0.0.1 and is not reachable from the network at all – only the proxy on port 8080 accepts external connections, and only through the tunnel.

Can this architecture handle multiple users with different API keys?

The single-key proxy described here is appropriate for internal tools and small teams. For multi-user deployments, extend the proxy to validate against a list of keys stored in environment variables or a local key-value file – each key mapped to a user identifier for logging purposes. Do not use a database for this unless your scale requires it: a simple dictionary loaded at startup is sufficient for dozens of keys and adds no operational complexity.

Does this architecture comply with UAE data sovereignty requirements for AI workloads?

Running Ollama on locally hosted hardware within the UAE means model inference and the data passed to it does not leave the country. Cloudflare Tunnel handles the network transit layer but does not process or store the request payload. Under the Federal Decree-Law No. 45 of 2021 (UAE Personal Data Protection Law – PDPL), if you are processing personal data in AI workflows, you remain the data controller and must ensure processing stays within compliant boundaries. A local Ollama deployment with this architecture satisfies the infrastructure side of that requirement – your legal obligations around consent, purpose limitation, and data subject rights apply on top of it.

Need a Secure Local LLM Deployment for Your UAE Business?

The Orange Club designs and deploys AI integration solutions across Dubai and the UAE – from local LLM infrastructure and security architecture to full enterprise system integration. If you are building a production AI pipeline and need it done to UAE compliance standards, talk to our team.

See Our AI Integration Services →

The Orange Club – author

Leave a Reply

Your email address will not be published. Required fields are marked *

Connecting...