Deploy Django on PythonAnywhere
Deploy an existing Django project on PythonAnywhere with a virtualenv, a correct WSGI file, MySQL, static file mappings, environment variables, and forced HTTPS.
Problem statement
PythonAnywhere is a hosted Python environment with a browser-based console and a web app runner. It is often the first place a Django developer puts a project online, because there is no server to configure and no container to build.
Most failed PythonAnywhere deployments come from the same few misunderstandings:
- choosing the "Django" quickstart for an existing project, which creates a new, empty project instead
- editing the project's own
wsgi.pywhen PythonAnywhere actually uses a separate WSGI file under/var/www/ - forgetting to point the web app at the virtualenv, so imports fail
- leaving
DEBUG=Truebecause there is no obvious place for environment variables - expecting static files to be served without a mapping
This guide deploys an existing project from Git with production-appropriate settings.
Quick answer
- Open a Bash console, clone your repository, and create a virtualenv with the same Python version you will select for the web app.
- Install requirements into the virtualenv.
- Create a web app with Manual configuration and the matching Python version.
- Set the virtualenv path on the Web tab.
- Edit the PythonAnywhere WSGI file to add your project path, load a
.envfile, and setDJANGO_SETTINGS_MODULE. - Configure MySQL (or PostgreSQL on paid plans), run
migrateandcollectstatic. - Add a static files mapping for
/static/, enable Force HTTPS, and reload the web app.
Step-by-step solution
1. Clone the project
In a Bash console on PythonAnywhere:
cd ~
git clone https://github.com/example/mysite.git
cd mysite
ls
Note the absolute path of the folder that contains manage.py, for example /home/myusername/mysite. You need it for the WSGI file.
2. Create a virtualenv
PythonAnywhere includes virtualenvwrapper:
mkvirtualenv --python=/usr/bin/python3.13 mysite-venv
pip install -r requirements.txt
Use a Python version that PythonAnywhere offers for web apps on your account's system image, and that your Django version supports. The virtualenv lives at /home/myusername/.virtualenvs/mysite-venv. If a package needs compilation and fails to build, check whether a binary wheel exists for your Python version.
3. Create the web app
On the Web tab:
- Click Add a new web app.
- Choose Manual configuration (not the Django option; that one generates a new project).
- Select the same Python version as the virtualenv.
In the Virtualenv section, enter /home/myusername/.virtualenvs/mysite-venv.
4. Store settings outside the code
PythonAnywhere web apps do not read variables you export in a console. The practical approach is a .env file loaded from the WSGI file. Add python-dotenv to your requirements and create /home/myusername/mysite/.env:
DJANGO_SECRET_KEY=generated-secret
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=myusername.pythonanywhere.com
DB_NAME='myusername$mysite'
DB_USER=myusername
DB_PASSWORD='your-mysql-password'
DB_HOST=myusername.mysql.pythonanywhere-services.com
Single quotes matter for any value containing $: without them, the shell would expand $mysite to an empty string when you load the file in a console. Make sure .env is listed in .gitignore, and restrict its permissions:
chmod 600 /home/myusername/mysite/.env
Read the values in settings.py:
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS if h]
5. Edit the PythonAnywhere WSGI file
The Web tab links to a file such as /var/www/myusername_pythonanywhere_com_wsgi.py. This is the file the web server imports; your project's mysite/wsgi.py is not used. Replace its contents with:
import os
import sys
from dotenv import load_dotenv
project_home = "/home/myusername/mysite"
if project_home not in sys.path:
sys.path.insert(0, project_home)
load_dotenv(os.path.join(project_home, ".env"))
os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings"
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
project_home must be the folder that contains manage.py, and mysite.settings must match the package that contains settings.py.
6. Configure the database
On the Databases tab, set a MySQL password and create a database. PythonAnywhere prefixes database names with your username and a dollar sign, for example myusername$mysite.
Configure Django:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ["DB_HOST"],
"OPTIONS": {
"init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
"charset": "utf8mb4",
},
}
}
Install the driver in the virtualenv with pip install mysqlclient. Then apply migrations from a console with the virtualenv active:
workon mysite-venv
cd ~/mysite
set -a; . ./.env; set +a
python manage.py migrate
python manage.py createsuperuser
The set -a line exports the .env values into the console session so manage.py sees the same settings as the web app. PostgreSQL is available as a paid add-on if your project depends on PostgreSQL-specific features.
7. Static files
Set STATIC_ROOT and collect:
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
python manage.py collectstatic --noinput
On the Web tab, under Static files, add a mapping:
| URL | Directory |
|---|---|
/static/ | /home/myusername/mysite/staticfiles |
If you store user uploads locally, add a second mapping for /media/ pointing at MEDIA_ROOT. Static file mappings are served by PythonAnywhere's web server without passing through Django.
8. HTTPS
The myusername.pythonanywhere.com domain has HTTPS available. On the Web tab, enable Force HTTPS so HTTP requests redirect before reaching Django. Then turn on secure cookies:
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
For a custom domain (paid accounts), add the domain as a new web app, point a CNAME at the value PythonAnywhere shows, and set up the certificate from the Web tab.
9. Reload and verify
Click Reload on the Web tab after every code, WSGI, or .env change. Then:
- open
https://myusername.pythonanywhere.com/ - open
/admin/and confirm styling loads - check the error log and server log links on the Web tab if anything fails
From a console:
workon mysite-venv
cd ~/mysite
set -a; . ./.env; set +a
python manage.py check --deploy
Verification check:
- the site responds over HTTPS and HTTP redirects to it
- the error log shows no import errors after reload
- static files load with
200 DEBUGisFalse(a missing URL shows a plain "Not Found" page, not the yellow debug page)
Updating the site
workon mysite-venv
cd ~/mysite
git pull
pip install -r requirements.txt
set -a; . ./.env; set +a
python manage.py migrate
python manage.py collectstatic --noinput
Then reload the web app from the Web tab. For scripted reloads, PythonAnywhere also documents an API endpoint for reloading web apps with an API token.
Rollback
cd ~/mysite
git log --oneline -5
git checkout <previous-good-commit>
Reload the web app. If the bad release applied migrations, you may also need to reverse them with python manage.py migrate app_label <previous_migration>, so back up the database before migrating. MySQL backups can be taken with mysqldump from a console.
Explanation
PythonAnywhere runs your app through its own WSGI server configuration. The only contract between the platform and your project is the WSGI file in /var/www/: it must put your project on sys.path, set the settings module, and expose application. Because the web worker process does not inherit environment variables from consoles, loading configuration inside that WSGI file is the reliable way to keep secrets out of settings.py.
Edge cases / notes
Something went wrong / error log shows ModuleNotFoundError
The virtualenv path is wrong or empty, project_home points at the wrong folder, or the Python version of the web app differs from the virtualenv.
DisallowedHost
ALLOWED_HOSTS did not load, usually because .env was not found from the WSGI file. Log os.environ.get("DJANGO_ALLOWED_HOSTS") temporarily or check the path.
Outbound requests fail on a free account
Free accounts have restricted outbound internet access to an allowlist of sites. Calls to APIs that are not on the list fail; a paid account removes the restriction.
Scheduled jobs
Use the Tasks tab to run management commands on a schedule, calling the virtualenv's Python with the full path to manage.py.
Reading PythonAnywhere logs effectively
The Web tab links to three log files, each answering a different question:
- Error log: Python tracebacks from your WSGI application, including import errors when the app fails to load. This is the first place to look for "Something went wrong" pages.
- Server log: messages from the web worker process, such as worker restarts or harakiri-style timeouts on slow requests.
- Access log: every request with status code and response time, useful for spotting
404s on static files or slow endpoints.
The newest entries are at the bottom of each file. After a reload, scroll to the end before reading; old tracebacks from earlier attempts stay in the file.
To test the WSGI file directly from a console, import it the same way the web worker does:
workon mysite-venv
python /var/www/myusername_pythonanywhere_com_wsgi.py && echo "WSGI file imports cleanly"
If this prints a traceback, fix it before reloading; the web app will fail with the same error.
Performance and limits
PythonAnywhere runs a fixed number of web workers per web app depending on your plan. A slow view blocks one of those workers for its whole duration, so a few slow endpoints can make the site feel unavailable. Practical measures:
- keep database queries efficient and add indexes for filters used on list pages
- cache expensive pages or fragments with Django's cache framework (the database cache backend works without extra services)
- move long-running work (report generation, bulk emails) to scheduled tasks or always-on tasks on plans that include them
- serve all static and media files through static file mappings, never through Django views
Security checklist for PythonAnywhere
DEBUGisFalseandSECRET_KEYcomes from.env, not fromsettings.pyin Git..envhas mode600and is listed in.gitignore.- Force HTTPS is enabled and secure cookie settings are on.
- The admin URL uses a strong password, and ideally a less predictable path than
/admin/. - Password-protection on the Web tab is enabled for staging or unfinished sites so search engines and visitors cannot reach them.
Common mistakes
- Running
pip installoutside the virtualenv, so packages land in the user site-packages for a different Python version. - Editing code and forgetting to reload, then debugging behavior that comes from the old code in memory.
- Pointing a static files mapping at the app's
static/source folder instead ofSTATIC_ROOT, so admin assets are missing. - Using
ALLOWED_HOSTS = ["*"]to silence errors instead of listing the real domain.
Next steps
- Review MySQL-specific settings in Configure Django with MySQL in Production.
- Learn configuration patterns in Configure Django Environment Variables for Production.
- Diagnose errors with Fix Django 500 Internal Server Error in Production.
FAQ
Which WSGI file does PythonAnywhere use for Django?
The one linked from the Web tab, located under /var/www/ and named after your domain. Your project's own wsgi.py is not used unless you import it from that file.
How do I set environment variables for a Django app on PythonAnywhere?
Put them in a .env file outside version control and load it with python-dotenv at the top of the PythonAnywhere WSGI file. Load the same file in consoles before running management commands.
Why do I need to reload the web app after changes?
The web worker keeps your code imported in memory. Changes to Python files, the WSGI file, or environment values are only picked up after you click Reload or call the reload API.
Can I use PostgreSQL on PythonAnywhere?
Yes, on paid accounts as an add-on. MySQL is available on all accounts, and SQLite works for very small sites but is not recommended for concurrent writes.