Deploy a Private RAG App on Windows Server 2025

What you will build
This tutorial shows you how to deploy a private RAG app on Windows Server 2025 with IIS as the front end, a Python FastAPI backend, PostgreSQL for app data, and Qdrant for vector search. It assumes a fresh VPS or dedicated server and keeps the full stack private to your server, so you control data, access, and backup policy.
You will start from a root-style first login, create a named admin workflow in Windows terms, install the required runtime, configure IIS as a reverse proxy, lock down the application port, and verify the app with real requests. If you are planning this on a Hostperl VPS, Hostperl VPS is the right fit for most small-team RAG deployments; larger document libraries or higher concurrency may justify a dedicated server instead.
For production, this layout gives you three practical advantages: the web tier stays on port 443, the app tier stays private on localhost, and your vector store can be backed up separately from the application files. If you want to compare the backup angle with a real recovery flow, see RAG hosting for agencies, security, latency, and backups and private AI inference capacity planning on VPS in 2026.
Architecture and decision points
This build uses Windows Server 2025 because the selected OS track for this tutorial is Windows Server, and because IIS works cleanly with PowerShell, Windows Firewall, Event Viewer, and TLS management. The stack stays simple: IIS terminates HTTPS, URL Rewrite and ARR forward requests to FastAPI on 127.0.0.1:8000, PostgreSQL stores documents and metadata, and Qdrant holds embeddings for retrieval.
- IIS: public entry point, TLS, logging, and reverse proxy.
- FastAPI: RAG API, document upload, retrieval, and answer generation.
- PostgreSQL: users, documents, conversation state, and job metadata.
- Qdrant: vector search for embeddings.
This design keeps sensitive services off the public network. If you later need queue workers, you can add them without changing the external port layout.
1) Connect to the server and identify Windows Server
On your local computer
ssh root@203.0.113.10203.0.113.10 is a reserved documentation example. Replace it with the real public IP assigned to your server. If your provider gives you a default Windows administrator account, connect with that account instead of root.
On the VPS as root or the initial administrator in PowerShell
Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsHardwareAbstractionLayerYou should see Windows Server 2025 or the exact Windows Server edition you deployed. That confirms the platform before you touch IIS or firewall rules.
2) Patch the server and create a named admin workflow
Windows does not use a sudo group, but the operational idea is the same: keep the initial admin session open, create a named administrator account, and test that account before you lock access down.
On the VPS as root or the initial administrator in PowerShell
$Password = Read-Host "Enter a strong password" -AsSecureString
New-LocalUser -Name "deploy" -Password $Password -FullName "Deployment Admin" -Description "Admin account for RAG app deployment"
Add-LocalGroupMember -Group "Administrators" -Member "deploy"This creates a dedicated admin account named deploy and gives it administrator rights. Keep the first session open until the new account works.
Now install the latest Windows updates.
Install-Module PSWindowsUpdate -Force
Import-Module PSWindowsUpdate
Get-WindowsUpdate
Install-WindowsUpdate -AcceptAll -AutoRebootAfter reboot, reconnect and continue with the new admin account. If updates fail, check Get-WindowsUpdateLog and fix that before moving on.
3) Install IIS, URL Rewrite, and Application Request Routing
On the VPS as the deploy administrator in PowerShell
Install-WindowsFeature Web-Server, Web-WebSockets, Web-Http-Errors, Web-Stat-Compression, Web-Dyn-Compression, Web-Filtering, Web-Request-Monitor, Web-Default-Doc, Web-Static-Content, Web-Http-LoggingNext, install the IIS reverse proxy components. On Windows Server, URL Rewrite and ARR are usually installed from Microsoft installers rather than Windows Features. Download them from Microsoft, then run the installers interactively or by command line if your packaging allows it. After installation, confirm the modules appear in IIS Manager under IIS > Modules.
For the FastAPI backend, install Python 3.12 or later from the official Windows installer, then verify it:
py -3.12 --versionYou should see a Python 3.12.x response. If Python is not registered with the launcher, use the full installer path and add it to PATH.
4) Install PostgreSQL and Qdrant
For a private RAG app, keep the database and vector store local unless you have a separate security or scaling reason to split them out. That keeps latency low and simplifies backups.
Install PostgreSQL with the Windows installer, then create a database and role from psql or pgAdmin. Use a dedicated database user instead of the built-in admin account.
CREATE USER ragapp WITH PASSWORD 'change-this-password-now';
CREATE DATABASE ragdb OWNER ragapp;For Qdrant on Windows Server 2025, use the Windows binary release or run it in Docker if your build policy prefers containers. This tutorial uses a Windows service approach so the app stays close to native IIS tooling. Start Qdrant on localhost only, then test the health endpoint.
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:6333/healthzYou should receive a small JSON or plain-text health response. If Qdrant is not healthy, check its service logs before moving on.
If you need the RAG design and storage split explained in more operational detail, RAG hosting for small teams: what to plan first is a useful companion read.
5) Create the app directory and Python virtual environment
On the VPS as the deploy administrator in PowerShell
New-Item -ItemType Directory -Path C:\opt\myapp -Force
Set-Location C:\opt\myapp
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pipThis creates the standard application directory and isolates dependencies in a virtual environment. That separation matters during upgrades and rollback.
Install the backend packages:
.\.venv\Scripts\pip.exe install fastapi uvicorn[standard] psycopg[binary] qdrant-client python-multipart pydantic-settingsNow check the installed versions:
.\.venv\Scripts\python.exe -c "import fastapi, uvicorn, psycopg, qdrant_client; print('ok')"If that prints ok, the runtime is ready.
6) Create the FastAPI app and configuration
Create the application file and a private environment file. Keep secrets out of IIS and out of source control.
On the VPS as the deploy administrator in PowerShell
notepad C:\opt\myapp\app.pyPaste this complete file content, then save and close Notepad.
from fastapi import FastAPI, HTTPException
from pydantic_settings import BaseSettings
import psycopg
from qdrant_client import QdrantClient
class Settings(BaseSettings):
database_url: str = "postgresql://ragapp:change-this-password-now@127.0.0.1:5432/ragdb"
qdrant_url: str = "http://127.0.0.1:6333"
app_name: str = "Private RAG App"
settings = Settings()
app = FastAPI(title=settings.app_name)
def db_ping():
with psycopg.connect(settings.database_url) as conn:
with conn.cursor() as cur:
cur.execute("SELECT 1")
return cur.fetchone()[0]
@app.get("/healthz")
def healthz():
try:
db_ok = db_ping()
qdrant = QdrantClient(url=settings.qdrant_url)
qdrant.health_check()
return {"status": "ok", "database": db_ok, "qdrant": "ok"}
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))
@app.get("/")
def index():
return {"message": "Private RAG app is running"}Now create the settings file:
notepad C:\opt\myapp\.envUse this content, then save and close it.
DATABASE_URL=postgresql://ragapp:change-this-password-now@127.0.0.1:5432/ragdb
QDRANT_URL=http://127.0.0.1:6333
APP_NAME=Private RAG AppRestrict permissions so only administrators and the service account can read the file:
icacls C:\opt\myapp\.env /inheritance:r
icacls C:\opt\myapp\.env /grant Administrators:F
icacls C:\opt\myapp\.env /grant deploy:R7) Register the app as a Windows service
Use NSSM or a similar service wrapper so the backend starts after reboot. Point it at the virtual environment Python executable and the app module.
On the VPS as the deploy administrator in PowerShell
New-Item -ItemType Directory -Path C:\opt\myapp\logs -Force
nssm install ragapp C:\opt\myapp\.venv\Scripts\python.exe
nssm set ragapp AppParameters -m uvicorn app:app --host 127.0.0.1 --port 8000 --proxy-headers
nssm set ragapp AppDirectory C:\opt\myapp
nssm set ragapp AppStdout C:\opt\myapp\logs\ragapp.out.log
nssm set ragapp AppStderr C:\opt\myapp\logs\ragapp.err.log
nssm start ragappIf you prefer a service alternative, create it with PowerShell 7 and a scheduled task, but NSSM is simpler for most IIS-backed app services. After starting the service, confirm it is running:
Get-Service ragappYou should see Running.
For teams that want a Docker-based path later, the deployment discipline in Docker Compose release readiness for VPS launches is still relevant, even though this tutorial uses a Windows service.
8) Configure IIS as the reverse proxy
Create a new IIS site that listens on 80 and 443, then route traffic to the backend on localhost. Start with HTTP so you can test routing before adding TLS.
On the VPS as the deploy administrator in PowerShell
Import-Module WebAdministration
New-Website -Name "ragapp" -Port 80 -PhysicalPath "C:\inetpub\wwwroot" -ForceNow open IIS Manager and add a URL Rewrite rule that proxies all requests to http://127.0.0.1:8000. The complete XML depends on your IIS rewrite setup, but the essential behavior is simple: forward every request path to the app service and preserve the host header.
Before you touch HTTPS, test the local backend directly:
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/healthzYou should receive {"status":"ok"...} or a similar successful health response.
9) Open the firewall safely
Windows Firewall should allow only the public web ports and management access you actually need. Do not expose PostgreSQL or Qdrant to the internet.
On the VPS as the deploy administrator in PowerShell
New-NetFirewallRule -DisplayName "RAG App HTTP" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 80
New-NetFirewallRule -DisplayName "RAG App HTTPS" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 443
New-NetFirewallRule -DisplayName "RDP Admin" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3389If your provider already restricts RDP at the network edge, leave that rule in place only as long as you need it. Add the new rule first, test access, then remove old or broader rules later.
10) Add TLS with a public certificate
For production, bind IIS to HTTPS with a certificate from your preferred CA or from a managed certificate tool that supports Windows Server 2025. If the app is public-facing, this is not optional.
After certificate installation, bind it to site ragapp:
Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, Thumbprint, NotAfterSelect the certificate you want, then bind it in IIS Manager. After binding, confirm the HTTPS listener is present:
netstat -ano | findstr :443You should see IIS listening on port 443.
If you need DNS and TLS repair guidance for hosting operations, the workflow in DNSSEC and email authentication on Ubuntu Server 24.04 is useful for the certificate and zone-management side of the job, even though the OS differs.
11) Verify the app from the server and a client
On the VPS as the deploy administrator in PowerShell
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/healthz
Get-Service ragapp
Get-EventLog -LogName Application -Newest 20Expected results: the root endpoint returns the welcome JSON, the health endpoint returns ok, the service stays running, and the event log shows no repeated service crashes.
On your local computer
curl -I http://203.0.113.10
curl -k https://203.0.113.10/healthzReplace 203.0.113.10 with your server’s public IP if you are still testing by IP. Once DNS is live, use the hostname that points to the server. A working setup returns HTTP 200 or a healthy application response through IIS.
12) Back up and test rollback
Back up the application directory, the PostgreSQL database, and the Qdrant data directory. If you skip the restore test, you do not actually know your recovery path works.
On the VPS as the deploy administrator in PowerShell
Stop-Service ragapp
pg_dump -U ragapp -h 127.0.0.1 -Fc ragdb -f C:\opt\myapp\backups\ragdb.dump
Copy-Item C:\opt\myapp -Destination C:\opt\myapp-backup -Recurse -Force
Start-Service ragappThen perform a restore test in a staging copy or isolated window. If the application fails after a change, stop the service, restore the previous app folder, import the database backup, and start the service again. That is the fastest safe rollback path for this layout.
Troubleshooting the most likely failures
Backend does not answer on port 8000
Run:
Get-Service ragapp
Get-Content C:\opt\myapp\logs\ragapp.err.log -Tail 50If the service is stopped or the log shows import errors, fix the Python package or the app file, then start the service again with nssm start ragapp.
IIS returns 502 Bad Gateway
Run:
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/healthz
netstat -ano | findstr :8000If localhost fails, the app service is down. If localhost works, the IIS rewrite or ARR proxy rule is wrong. Recheck the destination URL and preserve-host setting.
Database connection errors
Run:
Test-NetConnection 127.0.0.1 -Port 5432
psql -U ragapp -h 127.0.0.1 -d ragdb -c "select now();"If PostgreSQL refuses the connection, confirm the service is running and the password in .env matches the database role.
Qdrant health check fails
Run:
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:6333/healthzIf the endpoint does not respond, start the Qdrant service and review its logs before testing the app again.
Roll forward safely after launch
Once the app is stable, keep the root or initial setup session closed, continue daily work with the deploy administrator account, and patch the server on a routine schedule. For larger teams, the next step is usually separating PostgreSQL and Qdrant backups, then moving the app to a staging slot before each release.
If you are planning to grow beyond a single Windows server, compare that workload against managed VPS hosting first. If your document corpus or concurrent users are growing quickly, a dedicated server can make more sense than stretching a small instance.
Hostperl can host the Windows Server side of a private RAG deployment with the storage, bandwidth, and support response you need for production launches. If you want a simpler starting point, Hostperl VPS hosting works well for small and medium internal knowledge bases, while larger or higher-concurrency deployments may fit better on a dedicated server.
Our team also sees real-world migration and recovery issues, so if you are moving a bot or knowledge base from a test box to production, plan the cutover with backups already restored once.
FAQ
Can I run this RAG stack on shared hosting?
No. You need a server where you can run IIS, PostgreSQL, Qdrant, and a persistent app service with firewall control.
Do I need Docker for this setup?
No. This tutorial uses native Windows services so IIS, Event Viewer, and Windows Firewall stay easy to manage.
Why keep PostgreSQL and Qdrant on localhost?
It reduces exposure and keeps retrieval fast. Open only the ports you actually need.
What should I back up first?
Back up the database, then the Qdrant data, then the app directory and environment file.
What is the safest rollback if a release breaks?
Stop the service, restore the previous app folder and database backup, then start the service again after a local health check passes.
