☁️ Ollama on a Cloud VM

Ollama on a Cloud VM / survival guide

Ollama on a Cloud VM (AWS, Azure and Google Cloud)

Run your own private AI on a rented server: no 8 GB laptop limit, and you can reach it from anywhere.
This guide builds it step by step, shows how to keep it secure, and how to keep the bill small.
The steps are the same on every cloud. Only the instance names differ.
Read "Costs" before you click Create. A GPU machine left running for a month can cost hundreds of dollars.
Prices, instance names and quotas change often. Confirm them on your cloud's pricing pages before you build.

First, the honest truth about "free tier"
The free VMs in the other guides (AWS t2/t3.micro, Azure B1s, GCP e2-micro) have about 1 GB of RAM.
That is too small to run any useful model. Even a 1B-parameter model needs about 1.5 GB to load.
To run Ollama in the cloud, plan on a paid machine: 16 GB of RAM for small models on the CPU, or a GPU for speed.
Use free trial credits for this, and set a budget alert first.

Pick a machine

CPU only (cheap and slow: fine for learning and light use)
  4 vCPU / 16 GB RAM    3B to 8B models, a few words per second     AWS m7i.xlarge    Azure D4s_v5    GCP e2-standard-4
  4 vCPU / 32 GB RAM    Up to 14B models                            AWS r7i.xlarge    Azure E4s_v5    GCP n2-highmem-4
GPU (much faster, much more expensive)
  NVIDIA T4, 16 GB VRAM      7B to 8B models, quick                AWS g4dn.xlarge    Azure NC4as_T4_v3    GCP n1 with a T4
  NVIDIA L4 or A10G, 24 GB   Up to about 27B (4-bit)               AWS g6.xlarge      Azure NV A10 series  GCP g2-standard-4
  A100 / H100                70B-class models, very costly         AWS p4/p5          Azure ND series      GCP a2/a3
Rules of thumb:
- Model size (4-bit) plus 20 to 30 percent for context must fit in memory. On a GPU it must fit in VRAM to be fast
- Start with the smallest machine that fits your model. You can resize a VM later
- Use Ubuntu Server 24.04 LTS as the operating system

GPU quotas (the first surprise)

New cloud accounts usually have a GPU quota of ZERO, so creating a GPU VM fails until you ask for more:
AWS      Service Quotas > EC2 > "Running On-Demand G and VT instances" > request at least 4 vCPUs
Azure    Subscriptions > Usage + quotas > filter "NC" or "NV" > request an increase
GCP      IAM & Admin > Quotas > filter "GPUs (all regions)" and your GPU type > request 1
Requests can take minutes to days, and free-trial accounts may not be allowed GPUs at all. Ask early.

Costs (approximate: check current prices)

CPU 4 vCPU / 16 GB       about $0.15 to $0.20 per hour       roughly $110 to $150 a month if left on
GPU with a T4            about $0.50 to $0.75 per hour       roughly $400 to $550 a month if left on
GPU with an L4 or A10G   about $0.80 to $1.20 per hour
Spot / preemptible VMs   Often 60 to 90 percent cheaper, but the cloud can take the VM back at any time
Also billed: disks (a 100 GB disk costs a few dollars a month even when the VM is stopped), public IPs, and data leaving the cloud.
Cost habits that matter most:
1. Set a budget alert BEFORE you start (AWS Budgets, Azure Cost Management, GCP Billing budgets)
2. STOP the VM when you are done. Stopped VMs do not bill for compute, but disks and static IPs still bill
3. On Azure, stop it from the portal or CLI so it is "Deallocated". Shutting down inside the OS does not stop billing
4. Use an idle auto-shutdown (script below)
5. Delete everything when the project ends (see Cleanup)

Step 1: Create the VM

Use the create-VM steps from the free VM guides for your cloud, with these settings:
Image        Ubuntu Server 24.04 LTS
Size         From the table above
Disk         100 GB or more of SSD (an 8B model is about 5 GB, a 27B model about 17 GB, and you will try several)
Login        SSH key only (no password login)
Firewall     Allow SSH (port 22) from YOUR IP only. Do NOT open port 11434 or 3000 to the internet
Public IP    Ephemeral is fine (it changes on restart). A static IP costs extra while idle
Region       Close to you, and one that has your GPU type in stock
Example CLI commands (names and flags change, so prefer the console the first time):
aws ec2 run-instances --image-id <ubuntu-24.04-ami> --instance-type g4dn.xlarge --key-name mykey --security-group-ids <sg-ssh-from-my-ip-only>
az vm create -g ollama-rg -n ollama-vm --image Ubuntu2404 --size Standard_NC4as_T4_v3 --os-disk-size-gb 100 --admin-username azureuser --generate-ssh-keys
gcloud compute instances create ollama-vm --machine-type=g2-standard-4 --image-family=ubuntu-2404-lts-amd64 --image-project=ubuntu-os-cloud --boot-disk-size=100GB
(GCP GPU machines need a few more flags. See the Google Cloud GPU docs for the exact command.)

Step 2: Connect and update

ssh -i ~/.ssh/mykey.pem ubuntu@VM_PUBLIC_IP          (user: ubuntu on AWS and GCP, azureuser on Azure)
sudo apt update && sudo apt upgrade -y
sudo reboot                                          # if the kernel updated; reconnect after a minute
free -h        df -h        lscpu | head             # confirm RAM, disk and CPUs

Step 3: GPU driver (GPU machines only: skip this on CPU machines)

nvidia-smi                                           # already installed? It lists your GPU
sudo apt install -y ubuntu-drivers-common
sudo ubuntu-drivers devices                          # shows the recommended driver
sudo ubuntu-drivers autoinstall
sudo reboot
nvidia-smi                                           # after the reboot it should show the GPU, driver and memory
Shortcuts: AWS Deep Learning AMIs ship with drivers, Azure has an NVIDIA GPU Driver extension, and GCP has a driver installer.
If nvidia-smi still fails: lspci | grep -i nvidia (is the GPU there?), then dmesg | grep -i nvidia.

Step 4: Install Ollama

curl -fsSL https://ollama.com/install.sh | sh
systemctl status ollama                              # active (running)
ollama --version
The script detects an NVIDIA GPU and sets up support for it. AMD GPUs may need ROCm: see the Ollama docs.

Step 5: Keep models on a good disk (optional but smart)

For a separate data disk mounted at /data (check with lsblk first, and only format a NEW, EMPTY disk):
sudo mkfs.ext4 /dev/nvme1n1
sudo mkdir /data && sudo mount /dev/nvme1n1 /data
echo 'UUID=<uuid-from-blkid> /data ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab
sudo mkdir -p /data/ollama && sudo chown -R ollama:ollama /data/ollama
sudo systemctl edit ollama
[Service]
Environment="OLLAMA_MODELS=/data/ollama"
Environment="OLLAMA_KEEP_ALIVE=30m"
sudo systemctl daemon-reload && sudo systemctl restart ollama
A separate disk survives a VM rebuild, and you can snapshot it so models never need downloading twice.

Step 6: Pull a model and test it

ollama pull llama3.2                                 # about 2 GB: a quick test
ollama run llama3.2 "Say hello in five words."
ollama pull llama3.1:8b                              # about 5 GB: a good general model on 16 GB RAM
ollama ps                                            # the PROCESSOR column should say 100% GPU on a GPU machine
watch -n 1 nvidia-smi                                # GPU memory and load while it answers (Ctrl+C to exit)
ollama run llama3.1:8b --verbose "Write a haiku about servers."      # prints tokens per second at the end
If ollama ps says "100% CPU" on a GPU machine, the driver is not working. See Common problems.

Step 7: Reach it securely

Ollama has NO password. Never expose port 11434 to the internet. Pick ONE of these methods.

Method A: SSH tunnel (simplest and safest)

Ollama stays on 127.0.0.1 on the VM. Your laptop forwards a local port through SSH:
ssh -N -L 11434:localhost:11434 -i ~/.ssh/mykey.pem ubuntu@VM_PUBLIC_IP
Leave it running. On your laptop, http://localhost:11434 now reaches the VM:
curl http://localhost:11434/api/tags
OLLAMA_HOST=127.0.0.1:11434 ollama run llama3.1:8b       # the ollama command on your laptop talks to the VM
Keep the tunnel alive with:  autossh -M 0 -N -L 11434:localhost:11434 ubuntu@VM_PUBLIC_IP

Method B: A private network (Tailscale or WireGuard)

curl -fsSL https://tailscale.com/install.sh | sh && sudo tailscale up       # on the VM and on each of your devices
tailscale ip -4                                                             # the VM's private address, for example 100.64.1.2
sudo systemctl edit ollama   ->   Environment="OLLAMA_HOST=100.64.1.2:11434"
sudo ufw allow in on tailscale0 to any port 11434 proto tcp
Use http://100.64.1.2:11434 from your devices. Nothing is exposed to the public internet, and the address does not change when the VM restarts.

Method C: HTTPS reverse proxy with a login (for apps and teammates)

sudo apt install -y caddy
caddy hash-password                                  # prints a hash for the password you choose
# /etc/caddy/Caddyfile
ai.example.com {
    basic_auth {
        alice PASTE_THE_HASH_HERE
    }
    reverse_proxy localhost:11434
}
sudo systemctl reload caddy
Open ports 80 and 443 (not 11434) in the cloud firewall. Clients send the login:
curl -u alice:PASSWORD https://ai.example.com/api/tags
Use a long random password. Public endpoints get scanned within hours.

Step 8: Add a chat interface (optional)

sudo apt install -y docker.io && sudo usermod -aG docker $USER            # log out and back in
docker run -d --name open-webui --restart unless-stopped -p 127.0.0.1:3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data ghcr.io/open-webui/open-webui:main
ssh -N -L 3000:localhost:3000 ubuntu@VM_PUBLIC_IP                          # then open http://localhost:3000 on your laptop
Binding to 127.0.0.1:3000 keeps it off the internet. Create the first (admin) account right away.
The container must reach Ollama: use http://host.docker.internal:11434 in its settings, set OLLAMA_HOST=0.0.0.0:11434 on the VM,
and keep port 11434 blocked in the cloud firewall (or use --network=host).

Step 9: Auto-stop when idle (protect your wallet)

Save this as /usr/local/bin/idle-shutdown.sh and chmod +x it. It shuts down after 60 minutes with no SSH sessions and no Ollama requests:
#!/usr/bin/env bash
set -euo pipefail
IDLE_MIN=60
# any SSH sessions (including tunnels)?
[ "$(ss -Htn state established '( sport = :22 )' | wc -l)" -gt 0 ] && exit 0
# any Ollama API requests recently?
[ "$(journalctl -u ollama --since "${IDLE_MIN} minutes ago" | grep -c '\[GIN\]')" -gt 0 ] && exit 0
# any model loaded right now?
ollama ps | tail -n +2 | grep -q . && exit 0
logger "idle-shutdown: no activity for ${IDLE_MIN} minutes, shutting down"
/sbin/shutdown -h now
Run it every 10 minutes:  echo '*/10 * * * * root /usr/local/bin/idle-shutdown.sh' | sudo tee /etc/cron.d/idle-shutdown
Test it with a short IDLE_MIN first. What "shutdown" does on each cloud:
AWS     Stops the instance (EBS-backed default): compute billing stops, disk billing continues
GCP     Stops the instance: compute billing stops
Azure   Shuts down but does NOT deallocate, so billing continues. Deallocate from outside: az vm deallocate -g ollama-rg -n ollama-vm
Start and stop from your laptop:
aws ec2 stop-instances --instance-ids i-0abc123          aws ec2 start-instances --instance-ids i-0abc123
az vm deallocate -g ollama-rg -n ollama-vm               az vm start -g ollama-rg -n ollama-vm
gcloud compute instances stop ollama-vm                  gcloud compute instances start ollama-vm
Public IPs change on restart. Look up the new address, or use Tailscale, whose address stays the same.

Step 10: Keep your downloaded models

- Keep the VM and just stop it (you pay only for the disk)
- Snapshot the data disk or the VM (AWS EBS snapshot or AMI, Azure disk snapshot, GCP snapshot). Snapshots cost a little per GB per month
- Store models on a separate persistent disk and reattach it to new VMs
du -sh /data/ollama        ollama list        ollama rm MODEL

Use it from your own tools

curl http://localhost:11434/api/generate -d '{"model":"llama3.1:8b","prompt":"Explain RAID levels briefly.","stream":false}' | jq -r .response
Python and OpenAI-style tools: base URL http://localhost:11434/v1 and API key "ollama". Editor plugins, Open WebUI and shell helpers use the same address.

Monitoring

watch -n 1 nvidia-smi                       GPU memory and utilization
htop                                        CPU and RAM (CPU machines)
ollama ps                                   Loaded models, and GPU or CPU
journalctl -u ollama -f                     Server log: requests, errors, GPU detection
df -h /data                                 Disk space
Cloud budget alerts can also warn you when a VM has been running too long.

Cleanup When Done

1. Terminate or delete the VM
2. Delete leftover disks (EBS volumes, managed disks, persistent disks)
3. Release static or elastic IP addresses
4. Delete snapshots and images you no longer need
5. Delete firewall rules, security groups and load balancers you created
6. On Azure, deleting the whole resource group is easiest
7. Check the billing page the next day to confirm charges have stopped

Common problems

GPU quota error or "not enough capacity"           Request a quota increase, or try another region or zone. GPUs run out of stock
nvidia-smi not found or no devices                 Install the driver (Step 3) and reboot. Check lspci | grep -i nvidia
ollama ps shows 100% CPU on a GPU machine          Driver problem: check nvidia-smi, then sudo systemctl restart ollama and read journalctl -u ollama
"model requires more system memory"                Use a smaller model or quantization, or a bigger VM
Answers are very slow                              CPU machine, or a model too big for the GPU. Use a smaller model and check ollama ps
Out of disk space while pulling                    Grow the disk, or move OLLAMA_MODELS to a bigger one
Tunnel says "connection refused"                   On the VM run systemctl status ollama and curl localhost:11434
Tunnel keeps dropping                              Use autossh, or set ServerAliveInterval 30 in ~/.ssh/config
Cannot SSH after a restart                         The public IP changed. Look up the new one, or use a static IP or Tailscale
Bill is higher than expected                       A VM, disk, static IP or snapshot was left behind. Check the cost report by resource
Spot VM disappeared                                The cloud took it back. Keep models on a persistent disk and expect interruptions
Open WebUI cannot see Ollama                       Container-to-host address problem: use host.docker.internal or host networking
Port scanned or abused                             You exposed 11434. Close it, use a tunnel or private network, and review the logs

Tips

    Set a budget alert first, then do everything else. It is the cheapest safety feature there is.

    Start on a CPU machine with a 3B to 8B model to learn the workflow. Move to a GPU only if you need the speed.

    Never expose Ollama to the internet. Use an SSH tunnel, a private network, or a proxy with a password and HTTPS.

    Put an idle auto-shutdown on every GPU machine, and remember to deallocate on Azure.

    Keep models on a separate disk you can snapshot, so a new VM is ready in minutes.

    Lock SSH to your own IP, use keys only, and keep the operating system updated.

    Use spot or preemptible instances for experiments, and regular instances for anything other people rely on.

    Compare the monthly cloud cost with a used GPU or a mini PC at home. Heavy daily use is often cheaper at home.

    Treat the VM as disposable: script the build (this guide as a shell script or an Ansible playbook) so you can recreate it in one command.

    Cloud names, prices and quotas change. Confirm details in your provider's documentation before relying on any number here.