While Nginx combined with Gunicorn or uWSGI is the standard deployment stack for Django applications, OpenLiteSpeed (OLS) offers a compelling alternative. OpenLiteSpeed delivers enterprise-grade performance, low memory footprint, asynchronous event-driven architecture, and a rich WebAdmin UI with native WSGI/FCGI process management and built-in LSCache integration.
In this guide, we'll walk through the complete production setup for deploying a Django backend on an OpenLiteSpeed server using lswsgi.
---
1. Server Access & Firewall Setup
Before setting up your application, secure initial server access and expose the necessary management ports.
SSH Access
Log in to your target server via SSH:
``
bash
ssh root@
`
Configure Firewall Rules
OpenLiteSpeed provides a web-based administration console on port 7080. Allow incoming traffic on this port via UFW (Uncomplicated Firewall):
`bash
ufw allow 7080/tcp
`
Retrieve WebAdmin Credentials
OpenLiteSpeed generates a random administrator password upon installation. Retrieve it from the installation directory:
`bash
sudo cat /usr/local/lsws/.litespeed_password
`
Note: You can now open your browser and navigate to
http:// to access the WebAdmin interface using the username admin and the retrieved password.
Activate Python Virtual Environment
If your server includes a pre-configured Python virtual environment for OpenLiteSpeed, activate it before managing dependencies:
`bash
source /usr/local/lsws/Example/html/bin/activate
`
---
2. GitHub SSH Authentication
To pull code securely from private or public repositories without embedding passwords, configure SSH key authentication.
Generate an Ed25519 SSH Keypair
Run ssh-keygen using the modern ed25519 algorithm:
`bash
ssh-keygen -t ed25519 -C "ols-production-server"
`
(Press Enter three times to accept the default file locations and empty passphrase for automated deployments).
Retrieve & Register the Public Key
Display the public key content:
`bash
cat ~/.ssh/id_ed25519.pub
`
Copy the printed public key string, then navigate to:
GitHub.com ➜ Settings ➜ SSH and GPG keys ➜ New SSH key
Paste the key, give it a descriptive title (e.g., OLS-Production-Server), and save.
---
3. Clone Repository & Setup Django Environment
Navigate to the default OpenLiteSpeed web directory to store your project files.
Clone the Backend Repository
`bash
cd /usr/local/lsws/Example/html
git clone git@github.com:/.git
cd /
`
Install Dependencies
Clean up OS-specific packages (such as pywin32 if developing on Windows) and install the requirement suite along with python-dotenv and gunicorn:
`bash
Strip Windows-only dependencies if present
sed -i '/pywin32/d' requirements.txt
Install python dependencies
pip install -r requirements.txt
pip install python-dotenv gunicorn
`
Database Migrations & Superuser Creation
Apply Django database migrations:
`bash
python manage.py migrate
`
Create an administrative account for Django Admin access:
`bash
python manage.py createsuperuser
`
(Enter your administrator credentials—such as username/email/phone and password—when prompted).
Collect Static Assets
Gather static files into the designated staticfiles folder for OpenLiteSpeed to serve directly:
`bash
python manage.py collectstatic --noinput
`
---
4. File Ownership & Permissions for OpenLiteSpeed
OpenLiteSpeed operates worker processes under the unprivileged user nobody and group nogroup. Ensure the web server process has read and execute permissions across the application tree.
Navigate back to the parent web root:
`bash
cd /usr/local/lsws/Example/html
`
Set recursive ownership:
`bash
chown -R nobody:nogroup .
`
Important: Using
. ensures hidden operational files (such as .env) receive proper ownership so Django can read environment variables at runtime.
---
5. OpenLiteSpeed WebAdmin Configuration
Navigate to http:// in your web browser and log in.
A. Configure App Server Context (
/)
1. Go to Virtual Hosts ➜ Select Example ➜ Click the Context tab.
2. Locate the URI / (App Server Context) and click Edit (📝) (or click + Add and select App Server if creating new).
3. Fill in the following context details:
- URI \*:
/
Location \*: /usr/local/lsws/Example/html/
Binary Path: /usr/local/lsws/fcgi-bin/lswsgi
Application Type: WSGI
Startup File:
Environment:
`text
PYTHONPATH=/usr/local/lsws/Example/html/lib/python3.12:/usr/local/lsws/Example/html//
LS_PYTHONBIN=/usr/local/lsws/Example/html/bin/python
`
4. Click Save.
B. Configure Static Context (
/static/)
To bypass WSGI overhead for static CSS, JS, and image assets:
1. Under Context, click + Add.
2. Select Static as the Context Type.
3. Enter the configuration settings:
- URI \*:
/static/
Location: /usr/local/lsws/Example/html/
Accessible \*: Yes
4. Click Save.
C. Configure Media Context (
/media/)
When DEBUG = False in production, Django stops serving user-uploaded media files. Configure OpenLiteSpeed to serve /media/ directly from disk with long-term browser caching:
1. In Virtual Hosts ➜ Example ➜ Context tab, click + Add:
- Context Type:
Static
URI \*: /media/
Location: /usr/local/lsws/Example/html/
Accessible \*: Yes
Enable Expires \*: Yes
Expires Default: A2592000 (30-day browser caching for uploaded media)
2. Click Save.
3. Set media folder ownership and directory permissions via SSH:
`bash
chown -R nobody:nogroup /usr/local/lsws/Example/html///media/
chmod -R 755 /usr/local/lsws/Example/html///media/
`
D. Configure OpenLiteSpeed LSCache for High-Performance API Caching
OpenLiteSpeed includes a high-performance caching module (LSCache) that can serve public GET API responses directly from memory/disk without touching Python.
1. Enable Cache Module Globally:
- Go to Server Configuration ➜ Modules tab.
- Edit the
cache module and set Enable Module = Yes. Click Save.
2. Setup Cache Policy on Virtual Host:
- Go to Virtual Hosts ➜ Select Example ➜ Click Cache tab.
- Edit Cache Policy: Set Enable Cache =
Yes, Cache Storage Path = /usr/local/lsws/Example/lscache. Click Save.
Ensure the directory exists with proper permissions:
`bash
mkdir -p /usr/local/lsws/Example/lscache
chown -R nobody:nogroup /usr/local/lsws/Example/lscache
`
3. Set Rewrite Rules in App Server Context (/):
- Go to Virtual Hosts ➜ Example ➜ Context tab ➜ Edit URI
/ (App Server Context).
Scroll down to the Rewrite Rules textarea and paste the following caching policy:
`apache
# Enable OpenLiteSpeed Cache engine
CacheLookup on
# 1. NEVER CACHE: Non-GET requests (POST, PUT, DELETE) OR sensitive endpoints (Auth, Billing, Admin, User Profile)
RewriteCond %{REQUEST_METHOD} !^GET$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/(admin|api/auth|api/checkout|api/user|api/feedback) [NC]
RewriteRule .* - [E=Cache-Control:no-cache]
# 2. CACHE PUBLIC READ-ONLY GET APIS (e.g. Products, Posts, Categories, Events) for 10 minutes (600s)
RewriteCond %{REQUEST_METHOD} ^GET$ [NC]
RewriteCond %{REQUEST_URI} ^/api/(posts|products|events|categories|public-info) [NC]
RewriteRule .* - [E=Cache-Control:max-age=600]
`
- Click Save.
E. Configure Python WSGI Worker Limits (App Server Tuning)
To prevent concurrency bottlenecks and optimize resource usage under heavy traffic:
1. Go to Server Configuration ➜ App Server tab.
2. Under Python WSGI Default Settings, click Edit (📝):
- Max Connections:
35 (Adjust based on VPS RAM, e.g. 35 for 1GB RAM, 100+ for 2GB/4GB RAM)
Environment:
`text
LSAPI_MAX_IDLE=60
LSAPI_CHILDREN=4
`
(Spawns 4 dedicated Python WSGI worker processes)
- Back Log:
50
Memory Soft Limit (bytes): 1500M
Memory Hard Limit (bytes): 2000M
Process Soft Limit: 500
Process Hard Limit: 600
3. Click Save.
F. Purge Cache & Graceful Restart
1. Go to Tools ➜ Purge All Cache.
2. Click the Graceful Restart button (🗘) at the top right of the WebAdmin portal to apply all changes.
---
6. Access Verification & Health Checks
Once restarted, verify the operational status of your Django backend endpoints:
| Endpoint | Target URL | Description |
| :--- | :--- | :--- |
| Django Admin Portal | http:// | Log in using superuser credentials |
| Swagger API Documentation | http:// | Interactive OpenAPI documentation |
| ReDoc API Documentation | http:// | Clean API reference UI |
⚡ Verify LSCache Hits
Verify that OpenLiteSpeed is serving cached API responses directly:
`bash
curl -I http:///api/posts/
`
- First Request: Returns
X-LiteSpeed-Cache: miss (Django handles request and populates cache)
Second Request: Returns X-LiteSpeed-Cache: hit (Served directly by OpenLiteSpeed engine!)
---
7. Summary Checklist
- [x] Firewall port
7080 opened and WebAdmin password retrieved.
[x] SSH key generated ( ed25519) and registered with GitHub.
[x] Repository cloned to /usr/local/lsws/Example/html/.
[x] Virtual environment activated and requirements.txt installed.
[x] Database migrations and static collection executed.
[x] Ownership assigned recursively to nobody:nogroup.
[x] OpenLiteSpeed App Server context ( /) mapped to lswsgi with PYTHONPATH.
[x] Static context ( /static/) and Media context (/media/) configured for asset delivery.
[x] LSCache policy and rewrite rules configured for GET API response acceleration.
[x] Python WSGI worker process limits tuned ( LSAPI_CHILDREN=4, LSAPI_MAX_IDLE=60).
[x] Graceful restart performed and LSCache header hits ( X-LiteSpeed-Cache: hit) verified.
---
8. Frequently Asked Questions (FAQs)
Q1: Why use
lswsgi instead of running Gunicorn behind an Nginx reverse proxy?
lswsgi is OpenLiteSpeed's native, high-performance WSGI engine. It manages Python worker processes directly within the web server without needing an intermediate reverse proxy layer (like Nginx ➜ Gunicorn). This architecture delivers lower HTTP latency, significantly reduced RAM consumption, and simplifies your infrastructure into a single unified web server.
Q2: Why are static files (
/static/) or media files (/media/) returning 404 or 403 Forbidden errors?
Static/media file issues usually stem from two common misconfigurations:
1. Missing Context: You haven't added a Static Context for /static/ or /media/ pointing directly to your Django project's asset directories in WebAdmin.
2. Permission Mismatches: The OpenLiteSpeed process user (nobody:nogroup) lacks read permissions. Make sure you run python manage.py collectstatic --noinput followed by chown -R nobody:nogroup . inside your web root.
Q3: How do I load environment variables (
.env) safely in OpenLiteSpeed?
The cleanest approach is using python-dotenv inside your Django settings.py or wsgi.py. Alternatively, you can define environment variables directly in the WebAdmin Context ➜ Environment configuration block. Ensure your .env file is owned by nobody:nogroup with 600 or 640 file permissions so OpenLiteSpeed can read it securely.
Q4: How do I restart the Python app workers after pulling new code?
After fetching updates (git pull), running migrations, and collecting static files, you can restart your WSGI workers by:
- Clicking the Graceful Restart button (🗘) in the top-right corner of WebAdmin, or
- Running
touch in the terminal to trigger an automatic WSGI reload without downtime.
Q5: How do I enable free SSL/TLS certificates (Let's Encrypt) on OpenLiteSpeed?
OpenLiteSpeed integrates natively with certbot. After obtaining your certificate (certbot certonly --webroot -w /usr/local/lsws/Example/html -d yourdomain.com), go to Virtual Hosts ➜ Example ➜ SSL Tab in WebAdmin. Set your key and certificate paths, enable HTTPS on port 443, and set SECURE_SSL_REDIRECT = True in your Django settings.py.
Q6: How do I invalidate or purge LSCache when data is modified in Django Admin or via API?
You can purge cache entries in three ways:
1. Global Purge: Navigate to WebAdmin ➜ Tools ➜ Purge All Cache.
2. Django Signals / Headers: Send an X-LiteSpeed-Purge: * response header from Django signal handlers (e.g. post_save, post_delete) to automatically clear cached API routes whenever model instances change.
3. Targeted URI Purge: Use rewrite rules or HTTP purge headers targeting specific endpoints (e.g. /api/posts/).
Q7: How do I resolve 503 Service Unavailable errors or memory limit crashes under load?
503 errors on OpenLiteSpeed typically occur when Python WSGI worker processes exceed allocated RAM or crash due to missing dependencies. To resolve this:
- Increase Memory Soft Limit (
1500M) and Memory Hard Limit (2000M) under Server Configuration ➜ App Server.
Ensure LSAPI_CHILDREN matches your CPU/RAM budget (e.g. 2 to 4 workers for 1GB RAM instances).
Inspect the OpenLiteSpeed error log at /usr/local/lsws/logs/error.log and stdout logs for Python tracebacks.
Q8: How should database connection pooling be configured with multiple
LSAPI_CHILDREN processes?
Since each LSAPI_CHILDREN worker spawns independent Python interpreter processes, every worker opens separate database connections. Set CONN_MAX_AGE in Django's DATABASES setting to a reasonable value (e.g. 60 or 300` seconds) or deploy a database connection pooler like PgBouncer (for PostgreSQL) to prevent exhausting database connection limits under high concurrency.