Django/Apache/mod_wsgi Deployment Issue: Internal Server Error & Redirect Loop on cPanel (AlmaLinux 9)
I'm an intern
trying to deploy a Django application on a cPanel server, and I've run into a persistent "Internal Server Error" coupled with an Apache redirect loop. I've been troubleshooting for over two days with various AI helpers, but I can't pinpoint the exact cause. Any fresh perspectives ??
My Setup:
Server OS
: AlmaLinux 9.6 (Sage Margay)Hosting Environment
: cPanel (My cPanel account does not have the "Setup Python App" option, which is why I'm using a manual Apache/mod_wsgi configuration.)Django Deployment
: Apache + mod_wsgiNo .htaccess content at all
.Project Structure
: I have two Django instances (production and staging) for CI/CD,
each with its own settings, environment variables, database, and domain.
validator/settings/base.py
validator/settings/production.py
(inherits from base)validator/settings/staging.py
(inherits from base)Environment files are stored in
/etc/validator/envs
WSGI files are stored in
/etc/validator/wsgi/
Django project roots
:/home/<username>/<app_name>/
(prod) and/home/<username>/<app_name_test>/
(staging).Python Version
: My virtual environments are runningPython 3.12.9
, and my WSGI files assert this.
The Problem:
When I try to access either domain.in (production) or test.domain.in (staging), I receive an "Internal Server Error". My Apache error log (specifically /var/log/httpd/error_log) shows the following critical error message:
[Fri May 30 00:26:38.624027 2025] [core:error] [pid 253617:tid 253705] [client 45.115.89.80:26970] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.
This started after a server expiration/renewal and then when I configured new databases for staging and production, updating the respective .env files. Previously, it worked (sharing a database).
My Apache configuration:
I understand cPanel uses a "Direct Include Style" for Apache configs, where httpd.conf
includes files from directories like /etc/apache2/conf.d/userdata/....
My custom Django configurations are placed in these included directories.
- Production Domain Custom Apache Config (
/etc/apache2/conf.d/userdata/std/2_4/<username>/<domain.in>/<filename>.conf
):
ServerName <domain>.in
ServerAlias www.<domain>.in
ServerAlias <server_ip>
WSGIDaemonProcess django_app python-home=/home/<username>/app/venv python-path=/home/<username>/app user=<apache_user> group=<group_user>
WSGIProcessGroup app
WSGIScriptAlias / /etc/validator/wsgi/production.wsgi
Alias /static/ /home/<username>/app/staticfiles/
<Directory /home/<username>/app/staticfiles>
Require all granted
</Directory>
<Directory /etc/validator/wsgi>
<Files production.wsgi>
Require all granted
</Files>
</Directory>
ErrorLog /home/<username>/app/error.log
CustomLog /home/<username>/app/access.log combined
- Staging Domain Custom Apache Config (similar path for test.domain.in):
ServerName test.<domain>.in
ServerAlias www.test.<domain>.in
WSGIDaemonProcess test_app python-home=/home/<username>/app_test/venv python-path=/home/<username>/app_test user=<apache_user> group=<group_user>
WSGIProcessGroup app_test
WSGIScriptAlias / /etc/validator/wsgi/staging.wsgi
Alias /static/ /home/<username>/app_test/staticfiles/
<Directory /home/<username>/app_test/staticfiles>
Require all granted
</Directory>
<Directory /etc/validator/wsgi>
<Files production.wsgi>
Require all granted
</Files>
</Directory>
ErrorLog /home/<username>/app_test/error.log
CustomLog /home/<username>/app_test/access.log combined
3.Production Environment File (/etc/validator/envs/production.env
):
DBUSER=<dbuser>
DBPASSWORD=<dbpasswd>
DBNAME=<dbname>
DBHOST=localhost
DBPORT=3306
- Production WSGI File (
/etc/validator/wsgi/production.wsgi
):
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
ENV_FILE = Path('/etc/validator/envs/production.env')
load_dotenv(dotenv_path=ENV_FILE)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<django_root_app>.settings.production')
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
- Django settings/prod.py (similar structure for
settings/staging.py
):
import os
from .base import *
DEBUG = False
ALLOWED_HOSTS = [
"domain",
"www.domain",
"server_ip",
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.<sql>",
"USER": os.environ["DBUSER"],
"PASSWORD": os.environ["DBPASSWORD"],
"NAME": os.environ["DBNAME"],
"HOST": os.environ["DBHOST"],
"PORT": os.environ["DBPORT"],
}
}
Troubleshooting Steps I've Already Taken:
Checked Apache Error Logs: The main Apache log (/var/log/httpd/error_log) consistently shows the "Request exceeded the limit of 10 internal redirects" error. My Django app's custom logs (/home//app/error.log and _test/error.log) are empty or show no relevant errors, often indicating a problem before Django's logger can even kick in.
Permissions: I've meticulously checked and set chown -R medivali:webapps and chmod permissions (755 for directories, 644 for files, 664 for logs) on /home//app/, /home//app_test/, /etc/validator/envs/, and /etc/validator/wsgi/.
WSGIDaemonProcess User/Group: I've tried user=nobody group=webapps (as in config) and user=medivali group=medivali. Neither resolves the issue.
Python Version Assertion: Confirmed venv/bin/python --version is 3.12+ for both environments.
proxy_fcgi_module Conflict: I've added <FilesMatch ".(phtml|php[0-9]*)$">SetHandler None to my validator_conf.conf files to try and prevent the cPanel PHP-FPM handler from interfering.
DocumentRoot vs. WSGIScriptAlias: I understand WSGIScriptAlias / should override DocumentRoot for Django, but I've also tried creating an index.html in /home/medivali/public_html (the default DocumentRoot in the main VHost) to test basic Apache serving, but it still leads to the redirect loop or internal server error.
Database Credentials: I've triple-checked the DBUSER, DBPASSWORD, DBNAME, DBHOST, DBPORT in both .env files. I can connect to the databases directly from the server using mysql client.
ALLOWED_HOSTS: Confirmed correct domain names are in ALLOWED_HOSTS for both production and staging settings.
AI Consultations: Spent 2.5 days consulting ChatGPT, Gemini, Claude, and DeepSeek, trying various suggestions, but without success.