IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

Deploy a Private RAG App on Windows Server 2025

By Raman Kumar

Share:

Updated on Sep 3, 2026

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.10

203.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, OsHardwareAbstractionLayer

You 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 -AutoReboot

After 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-Logging

Next, 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 --version

You 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/healthz

You 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 pip

This 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-settings

Now 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.py

Paste 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\.env

Use 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 App

Restrict 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:R

7) 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 ragapp

If 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 ragapp

You 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" -Force

Now 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/healthz

You 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 3389

If 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, NotAfter

Select the certificate you want, then bind it in IIS Manager. After binding, confirm the HTTPS listener is present:

netstat -ano | findstr :443

You 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 20

Expected 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/healthz

Replace 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 ragapp

Then 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 50

If 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 :8000

If 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/healthz

If 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.