
You don’t need a managed vector database to ship RAG features. You can run Qdrant on a VPS with Docker, put it behind Nginx with HTTPS, and keep the database off the public internet.
The result is a production-ready endpoint you control. You get stable costs, persistent storage, and security settings you can verify.
This walkthrough sets up Qdrant on Ubuntu using Docker Compose. Then it publishes Qdrant through Nginx with Let’s Encrypt TLS.
Along the way, you’ll tighten the firewall, apply safer defaults, and run quick checks. The goal is to catch issues early.
What you’ll build (and what you won’t)
- Qdrant running in Docker with persistent volumes
- Nginx reverse proxy terminating HTTPS for a clean public URL (or optionally private-only)
- UFW firewall rules so only ports 22/80/443 are exposed
- Health checks and maintenance habits (logs, upgrades, backups)
You won’t build a full RAG pipeline here.
The goal is a reliable vector DB endpoint your application can call from a VPS, dedicated server, or private network.
Prerequisites and sizing (CPU, RAM, disk)
Qdrant performance depends on how many vectors you store, their dimensionality (for example 384, 768, 1536), and how often you update points.
In most deployments, CPU and disk matter more than GPU.
GPUs usually pay off for embedding generation. They rarely help the database itself.
- OS: Ubuntu 22.04/24.04 on a VPS or dedicated server
- Minimum (dev/small prod): 2 vCPU, 4–8 GB RAM, 40+ GB NVMe
- Comfortable prod start: 4 vCPU, 8–16 GB RAM, 80+ GB NVMe
- Storage: use NVMe if possible; vectors + payloads + snapshots grow faster than you expect
If you’re building RAG for a real site (support docs, product catalog, internal knowledge base), choose a VPS with headroom and consistent IO.
A HostMyCode VPS is a clean fit for Qdrant plus your app. If you don’t want to handle patching and monitoring, managed VPS hosting can help.
Step 1: Set up the server (updates, user, firewall)
SSH into your server as a sudo user.
sudo apt update && sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg ufw
Lock the firewall down to the ports you’ll actually use.
In this setup, Qdrant’s native port (6333) stays private. It should not be reachable from the internet.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
If you want a safer way to move configs and backups on and off the box, set up constrained SFTP access.
This guide pairs well with Set Up SFTP-Only Users on Ubuntu VPS.
Step 2: Install Docker Engine and Docker Compose
Install Docker from the official Ubuntu repository.
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Verify:
docker --version
docker compose version
Step 3: Create a persistent Qdrant deployment with Docker Compose
Create a directory for the stack.
Keeping it under /opt makes it easier to audit and back up later.
sudo mkdir -p /opt/qdrant
cd /opt/qdrant
Create compose.yml:
sudo tee /opt/qdrant/compose.yml > /dev/null <<'YAML'
services:
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
restart: unless-stopped
environment:
# Tune conservatively first; you can revisit after measuring.
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
volumes:
- qdrant_storage:/qdrant/storage
networks:
- qdrant_net
# Expose only to localhost; Nginx will be the public entry point.
ports:
- "127.0.0.1:6333:6333"
- "127.0.0.1:6334:6334"
networks:
qdrant_net:
volumes:
qdrant_storage:
YAML
Bring it up:
cd /opt/qdrant
sudo docker compose up -d
sudo docker ps
Quick health check from the VPS:
curl -s http://127.0.0.1:6333/ | head
curl -s http://127.0.0.1:6333/healthz
Why localhost binding matters: it prevents accidental internet exposure.
If you later want another server to talk to Qdrant directly (without public HTTPS), use a VPN (see Set Up WireGuard VPN on Ubuntu VPS for Admin Access) or a private VLAN.
Don’t open 6333 to the world.
Step 4: Put Qdrant behind Nginx with HTTPS (Let’s Encrypt)
You’ll publish Qdrant as https://qdrant.example.com.
Before you touch Nginx, create the DNS record:
- Add an A record:
qdrant.example.com→ your server IP
If you need a registrar/DNS provider, manage it through HostMyCode domains.
Install Nginx and Certbot:
sudo apt -y install nginx certbot python3-certbot-nginx
Create an Nginx site config:
sudo tee /etc/nginx/sites-available/qdrant.example.com > /dev/null <<'NGINX'
server {
listen 80;
server_name qdrant.example.com;
location / {
proxy_pass http://127.0.0.1:6333;
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;
# Basic timeouts for larger payloads (bulk upserts)
proxy_connect_timeout 10s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
}
NGINX
Enable the site and test:
sudo ln -s /etc/nginx/sites-available/qdrant.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Issue a TLS cert:
sudo certbot --nginx -d qdrant.example.com
After Certbot updates the server block for HTTPS, add a few hardening headers.
Edit the TLS server block that Certbot created:
sudo nano /etc/nginx/sites-available/qdrant.example.com
Inside the server { listen 443 ssl; ... } block, add:
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy no-referrer always;
add_header X-Frame-Options DENY always;
# Don’t cache API responses by default
add_header Cache-Control "no-store" always;
Reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
If you want a more detailed TLS checklist (chain issues, renewal failures, messy mixed configs), use: TLS setup guide tutorial (2026): Configure HTTPS with Let’s Encrypt on a VPS.
Step 5: Add authentication (recommended) and basic abuse controls
Qdrant is an API. If you expose it publicly, put an access-control layer in front of it.
A practical baseline is HTTP Basic Auth at Nginx. If your app has a fixed outbound IP, add an allowlist on top.
Option A: Basic Auth with htpasswd
Install tools and create a password file:
sudo apt -y install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd qdrantuser
Then in the TLS server block’s location / add:
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
Reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Test from your laptop:
curl -u qdrantuser 'https://qdrant.example.com/healthz'
Option B: IP allowlist (best when your app has a fixed IP)
If your application runs on one VPS or a known dedicated server IP, allow only that traffic.
In the location / block:
allow 203.0.113.10; # your app server
allow 198.51.100.25; # office/VPN egress
deny all;
Be careful. If your app’s outbound IP changes, you’ll lock yourself out.
Rate limiting (cheap insurance)
Even with auth, rate limiting helps when something goes wrong.
One common cause is a retry loop that turns into a CPU heater.
Add this to /etc/nginx/nginx.conf inside the http {} block:
limit_req_zone $binary_remote_addr zone=qdrant_rl:10m rate=5r/s;
Then inside the Qdrant location / block:
limit_req zone=qdrant_rl burst=20 nodelay;
Reload:
sudo nginx -t
sudo systemctl reload nginx
For a deeper rate-limiting playbook (whitelists, bot patterns, not breaking real users), see Nginx Rate Limiting Tutorial (2026).
Step 6: Create a test collection and upsert vectors
At this point, you can treat Qdrant as a secure HTTPS endpoint.
This is a minimal end-to-end test.
It uses a tiny vector size (4 dims) so you can validate the plumbing before you wire up an embedding model.
Create a collection:
curl -u qdrantuser -X PUT "https://qdrant.example.com/collections/demo" \
-H 'Content-Type: application/json' \
-d '{
"vectors": {
"size": 4,
"distance": "Cosine"
}
}'
Upsert two points with payloads:
curl -u qdrantuser -X PUT "https://qdrant.example.com/collections/demo/points?wait=true" \
-H 'Content-Type: application/json' \
-d '{
"points": [
{"id": 1, "vector": [0.1, 0.2, 0.3, 0.4], "payload": {"title": "Doc A", "url": "/docs/a"}},
{"id": 2, "vector": [0.2, 0.1, 0.0, 0.9], "payload": {"title": "Doc B", "url": "/docs/b"}}
]
}'
Search:
curl -u qdrantuser -X POST "https://qdrant.example.com/collections/demo/points/search" \
-H 'Content-Type: application/json' \
-d '{
"vector": [0.1, 0.2, 0.25, 0.35],
"limit": 2,
"with_payload": true
}'
If you get JSON results back, the full path (TLS → Nginx → Qdrant → persistence) is working.
Step 7: Persistence, backups, and safe upgrades
The volume qdrant_storage is where Qdrant stores data. That gives you persistence, not safety.
For safety you want backups. Ideally, you also want snapshots you have restored at least once.
Find where Docker stores the volume
sudo docker volume inspect qdrant_qdrant_storage | grep -E 'Mountpoint|Name'
On most systems it lives under /var/lib/docker/volumes/.
Back it up only if you understand consistency implications.
For clean restore points, Qdrant snapshots are usually the better option.
Create a snapshot (recommended approach)
Qdrant supports snapshots via its API.
Snapshot endpoints can vary by deployment preferences, so confirm in your Qdrant docs/version.
The operational rule stays the same: snapshot regularly, store copies off-box, and test restores.
A pragmatic pattern is:
- Create snapshots during low-traffic windows
- Copy snapshot files to offsite storage (object storage, another VPS, or backup server)
- Practice restoring into a staging Qdrant instance
For a broader, test-first backup approach (runbooks, restore drills, DNS cutovers, rollback), adapt: Disaster recovery tutorial (2026).
Upgrade Qdrant safely
Docker makes upgrades straightforward, but don’t upgrade on autopilot.
Read Qdrant release notes for breaking changes, especially across major versions.
cd /opt/qdrant
sudo docker compose pull
sudo docker compose up -d
sudo docker logs --since=5m qdrant | tail -n 200
Also check health:
curl -u qdrantuser -s https://qdrant.example.com/healthz
Step 8: Secure the endpoint like you mean it
Vector databases rarely stay “just vectors.”
Payloads often include internal URLs, ticket excerpts, customer text, and other sensitive content.
Treat Qdrant like any other database service.
- Don’t expose port 6333 publicly. Keep it bound to localhost as shown.
- Require authentication. Basic Auth is a baseline; IP allowlists are even better.
- Keep TLS auto-renewal healthy.
systemctl status certbot.timershould show enabled. - Log access. Nginx access logs matter when you’re chasing suspicious requests.
- Patch the OS. Old OpenSSL and Nginx versions still show up in real incidents.
If your Qdrant endpoint is only for internal workloads, don’t publish it publicly.
Put it behind a VPN and have Nginx listen on the VPN interface.
Step 9: Run Qdrant on a VPS with a real RAG stack (practical patterns)
Once Qdrant is stable, most RAG setups add three components:
- Embedding generator (turn text into vectors). This is where GPU can matter.
- Ingestion job (chunk docs, embed, upsert, store metadata).
- Query pipeline (embed question, search Qdrant, feed results to an LLM).
Two setups that pair well with Qdrant on VPS infrastructure:
- Local embeddings + local chat: run an open model server on another box and keep traffic private. If you’re exploring self-hosted chat UIs, see Self-Host Open WebUI With Ollama on a VPS.
- GPU LLM API for generation: keep Qdrant on a CPU VPS, and run your generator model on a GPU VPS. If you need an OpenAI-compatible endpoint, the approach in Run vLLM on a GPU VPS With OpenAI-Compatible API is a good match.
A common failure mode is trying to run everything on one small VPS.
Qdrant wants steady RAM and fast disk. Inference wants GPU/VRAM or lots of CPU.
If you start seeing queueing and timeouts, split the roles.
Troubleshooting checklist (fast diagnostics)
- Nginx returns 502: confirm Qdrant is up:
sudo docker logs qdrant --tail=200andcurl http://127.0.0.1:6333/healthz - Certbot renewal fails: check:
sudo certbot renew --dry-runand confirm ports 80/443 are open in UFW - Requests hang during upserts: increase Nginx
proxy_read_timeoutand check disk IO saturation (often the real culprit) - Disk fills up: vector workloads grow quietly; track
df -hand prune old snapshots/logs. If you’re already in trouble, use Disk Space Troubleshooting Tutorial (2026).
Summary: production-ready Qdrant on infrastructure you control
You now have Qdrant running with persistent storage, HTTPS via Nginx, and a safer network posture (no public 6333).
That’s enough to support RAG search, internal knowledge assistants, or semantic search features in your app.
If you want this to stay boring in production, use NVMe-backed storage and don’t starve the box of CPU.
A HostMyCode VPS is a solid home for Qdrant, and managed VPS hosting is there if you’d rather outsource patching, monitoring, and routine fixes.
If you’re building RAG features for a website or application, you’ll get cleaner, more predictable performance by keeping Qdrant on its own infrastructure instead of squeezing it onto shared hosting. Start with a HostMyCode VPS for consistent IO and full control, or choose managed VPS hosting if you want a team handling updates, TLS renewals, monitoring, and the usual operational sharp edges.
FAQ
Should I expose Qdrant directly on port 6333?
No for most production cases. Keep Qdrant bound to localhost (or a private interface) and publish it through Nginx with TLS and access controls.
Do I need a GPU to run Qdrant on a VPS?
No. Qdrant is a vector database; it benefits from CPU, RAM, and fast disk. GPUs are typically for embeddings and LLM inference, not the database layer.
How much RAM do I need for a real RAG project?
For small-to-medium collections, 8–16 GB RAM is a sensible starting point. If you store lots of payload metadata or run heavy concurrent search, plan higher and measure.
What’s the simplest way to protect a public Qdrant endpoint?
Put it behind Nginx TLS, add Basic Auth, and apply an IP allowlist if your app has stable egress IPs. Rate limit as a backstop.
Can I run Qdrant and my web app on the same VPS?
Yes, if the VPS has enough headroom and you watch disk IO. For busy sites, split them: Qdrant on one VPS, app/API on another, and keep traffic private.