Tidqom — Source-linked AI and developer tools
AITid
AI Tools

Secure Ollama with Nginx, HTTPS, and a Password

Stop exposing your raw Ollama port to the internet. Here is exactly how I put Ollama behind Nginx with a real SSL certificate and password protection so I can access my local models safely.

T
Tidqom Editorial
August 8, 2026 · 5 min read
Secure Ollama with Nginx, HTTPS, and a Password

The Problem: "Connection refused" or a naked API

Related: Fix Stable Diffusion Out Of Memory on a 6GB VRAM GPU →

You have a server at home with a decent GPU, and you installed Ollama. By default, it binds to 127.0.0.1:11434. If you try to reach it from your laptop at a coffee shop, you get a connection timeout.

If you just change the environment variable to OLLAMA_HOST=0.0.0.0, the port opens to the entire internet. Ollama has no built-in authentication. Anyone running a port scanner can find your IP, connect to port 11434, and start hammering your GPU with inference requests. I did this accidentally for two days and noticed my GPU fans spinning at 100% while I was asleep.

You need a reverse proxy. You want a domain name (like ai.yourdomain.com), full HTTPS encryption, and a hard password prompt before any traffic touches the Ollama daemon. I run Nginx for this because it handles streaming HTTP responses perfectly. Here is the exact setup running on my rig right now.

Step 1: Bind Ollama tightly to localhost

Related: Fix ComfyUI "Torch Not Compiled With CUDA Enabled" Error →

Before we build the proxy, we have to make sure Ollama is only listening to internal traffic. If you leave Ollama bound to 0.0.0.0, people can just bypass Nginx entirely by hitting port 11434 directly.

If you are running Ollama as a systemd service on Linux, you need to override the default configuration. Do not edit /etc/systemd/system/ollama.service directly, as your changes will be wiped out the next time you update Ollama.

Instead, run this command:

bash
sudo systemctl edit ollama

This opens a blank override file. Paste in exactly this:

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

Save and exit the editor. Then reload the systemd daemon and restart Ollama:

bash
sudo systemctl daemon-reload
sudo systemctl restart ollama

Verify it is only listening locally by checking your open ports:

bash
sudo ss -tulpn | grep 11434

You should see 127.0.0.1:11434 and not *:11434 or 0.0.0.0:11434. Your GPU is now safe from the public internet.

Step 2: Choose your SSL strategy

Related: A One-File Docker Compose Stack for Ollama and Open WebUI →

To get HTTPS, you need an SSL certificate. You cannot pass basic auth credentials over plain HTTP unless you want your password intercepted in plain text by every router between you and your house.

Advertisement — In Article

You have three main options for getting traffic to your home server securely.

MethodSetup timeCostBest for
Certbot (Let's Encrypt)15 mins$10/yr (domain)Accessing your API from normal web apps, Python scripts, and third-party UIs.
Tailscale / Wireguard5 minsFreePrivate access only. No external web apps can reach it without running the VPN client.
Self-signed cert2 minsFreeLocal network only. Python and Node scripts will throw ugly SSL validation errors.

I use Let's Encrypt. I bought a cheap domain, pointed an A record to my home public IP address, and forwarded ports 80 and 443 through my router to my Linux server.

Once your domain is pointing to your server, install Nginx and Certbot:

bash
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

Run Certbot to grab your certificate. Replace the domain with yours:

bash
sudo certbot certonly --nginx -d ai.yourdomain.com

I choose certonly because I want to write the Nginx server block manually. Automatic Nginx configurations from Certbot usually strip out the specific proxy flags we need for streaming LLM responses.

Step 3: Generating the basic auth file

Related: Fixing Painfully Slow Whisper Transcription →

Next, we create the file that holds your username and hashed password. Nginx uses this to challenge incoming requests.

You need the Apache utilities package to get the htpasswd command:

bash
sudo apt install apache2-utils

Create a new password file and add a user. I will use the username ollamauser. The -c flag creates a new file. Do not use -c if you are adding a second user later, or it will overwrite the file.

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

You will be prompted to type a password twice. Make it long.

Check that the file was created and contains a hashed string:

bash
cat /etc/nginx/.htpasswd

You should see something like ollamauser:$apr1$xyz...

Step 4: Writing the Nginx configuration

Related: Fix Open WebUI Showing No Models in the Dropdown →

This is the most critical part. Ollama does not return a single block of JSON when you prompt it; it streams the response token by token using chunked transfer encoding. If you configure Nginx poorly, it will buffer the entire response and you will sit staring at a blank screen for two minutes before the text suddenly appears all at once.

Advertisement — In Article

Create a new Nginx configuration file:

bash
sudo nano /etc/nginx/sites-available/ollama

Paste in the following configuration. Change ai.yourdomain.com to your actual domain.

nginx
server {
    listen 80;
    server_name ai.yourdomain.com;
    return 301 https://$host$request_uri;
}

server { listen 443 ssl; server_name ai.yourdomain.com;

ssl_certificate /etc/letsencrypt/live/ai.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ai.yourdomain.com/privkey.pem;

Basic Auth setup

terminal
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;

Allow preflight CORS requests without authentication

terminal
# This is vital for web-based frontends
if ($request_method = OPTIONS) {
    return 204;
}

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

Disable buffering so token streaming works instantly

terminal
    proxy_buffering off;
    
    # Massive timeouts because LLMs take a long time to think
    proxy_read_timeout 1800s;
    proxy_connect_timeout 1800s;
    proxy_send_timeout 1800s;

Required for streaming chunks

terminal
    chunked_transfer_encoding on;
}

}

terminal

A few notes on why this looks the way it does. The proxy_buffering off; line is what allows you to see the text typing out in real-time.

The proxy_read_timeout 1800s; is equally important. If you are reading up on how much RAM you actually need for local AI and pushing your hardware to the absolute limit by offloading to system RAM, your time-to-first-token might be 45 seconds. Nginx defaults to dropping the connection after 60 seconds of silence. If you are experiencing what looks like Ollama running slow and abruptly terminating, it is usually Nginx killing the connection. Setting this to 1800 seconds (30 minutes) gives your GPU all the time it needs.

Enable the site and test the config:

bash
sudo ln -s /etc/nginx/sites-available/ollama /etc/nginx/sites-enabled/
sudo nginx -t

If it says syntax is ok, restart Nginx:

bash
sudo systemctl restart nginx

Step 5: Modifying Ollama's environment variables for CORS

Related: How to Stop Ollama From Unloading Models (keep_alive) →

At this point, if you hit https://ai.yourdomain.com from a terminal using curl, passing your password, it works perfectly. But if you try to connect a web-based frontend (like ChatGPT-style web UIs) hosted on another domain, it will fail silently.

If you open the browser console, you will see a nasty CORS error. This happens because the web browser sends an OPTIONS preflight request, and Ollama rejects it because the origin doesn't match. Nginx is letting the OPTIONS request through (because of the if ($request_method = OPTIONS) block we added above), but Ollama itself is blocking it.

Advertisement — In Article

We need to tell Ollama to accept requests from anywhere, since Nginx is already acting as our security bouncer.

Open the systemd override file again:

bash
sudo systemctl edit ollama

Add the OLLAMA_ORIGINS variable to the [Service] block so it looks like this:

ini
[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"
Environment="OLLAMA_ORIGINS=*"

Reload and restart:

bash
sudo systemctl daemon-reload
sudo systemctl restart ollama

Setting origins to a wildcard * is completely safe here because no request can reach Ollama without first passing the Nginx basic auth check. This exact CORS setup is mandatory if you are researching how MCP servers explained work and want to safely connect external agent tools to your local host.

To test your new secure setup, use a curl command that passes your basic auth credentials natively:

bash
curl -u ollamauser:yourpassword https://ai.yourdomain.com/api/tags

You should see a JSON array of your installed models. If you get an HTML page complaining about a 401 Unauthorized error, your password file path in the Nginx config is wrong.

What did NOT work

I spent hours fighting this setup before landing on the config above. Here are the dead ends I hit so you don't repeat them:

Handling CORS entirely in Nginx

I initially tried to fix the CORS errors by using Nginx add_header Access-Control-Allow-Origin * directives. This caused a horrific bug where Ollama was also trying to send CORS headers, resulting in duplicated headers. Web browsers strictly refuse requests with duplicate CORS headers. Modifying OLLAMA_ORIGINS and letting Ollama handle the headers itself is the only clean way.

Docker networking assumptions

I originally ran Nginx on the host machine but had my UIs in Docker containers. I spent an entire afternoon debugging why Open WebUI in Docker cannot reach Ollama. I kept pointing my Docker containers to https://ai.yourdomain.com, but my home router did not support NAT loopback (hairpinning). The Docker containers were trying to go out to the public internet and back into my own IP, which my router dropped. I had to add my domain to the Docker container's /etc/hosts file pointing to 172.17.0.1 (the host IP) to fix it.

Using API Keys via custom headers

I tried to set up Nginx to look for a custom Authorization: Bearer <token> header instead of Basic Auth. Nginx requires the commercial "Nginx Plus" or the Lua module to easily validate bearer tokens against a static file. Setting up Lua on an Ubuntu Nginx package was a massive headache. Standard Basic Auth achieves the exact same security with built-in tools.

FAQ

How do I connect the official Python script to this?

You pass the credentials in the client instantiation. Just define the base_url as your new HTTPS domain, and add an auth tuple or an Authorization header containing your base64 encoded username and password.

Can I run Ollama on a different port behind Nginx?

Yes. Just change OLLAMA_HOST=127.0.0.1:8080 in the systemd override file, and then update the proxy_pass http://127.0.0.1:8080; line in your Nginx configuration.

Does basic auth break local CLI usage on the server?

No. If you SSH into the server and type ollama run llama3, the CLI talks directly to the local port 11434. It bypasses Nginx completely, so it does not ask you for a password.

Why do I get a 502 Bad Gateway error?

Ollama is offline or Nginx has the wrong proxy port. Ensure you ran sudo systemctl restart ollama. Run curl http://127.0.0.1:11434 locally on the server; if that fails, Ollama is dead.

Advertisement

مواضيع مقترحة · Suggested Topics

استكشف مواضيع ومحاور ذات صلة بهذا المقال — روابط داخلية لتعميق قراءتك.

The Daily Pulse

Newsletter delivery is not connected yet. This form only saves your address in this browser; no email is sent.

Get concise, source-linked technology notes without the hype.

Advertisement