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.py when PythonAnywhere actually uses a separate WSGI file under /var/www/
  • forgetting to point the web app at the virtualenv, so imports fail
  • leaving DEBUG=True because 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.

Django on PythonAnywhereWeb tabmanual configurationWSGI filesys.path, settingsVirtualenvproject packagesStatic mappingsserved by platformThe platform WSGI file, not your project's wsgi.py, is what the web worker imports.

Quick answer

  1. Open a Bash console, clone your repository, and create a virtualenv with the same Python version you will select for the web app.
  2. Install requirements into the virtualenv.
  3. Create a web app with Manual configuration and the matching Python version.
  4. Set the virtualenv path on the Web tab.
  5. Edit the PythonAnywhere WSGI file to add your project path, load a .env file, and set DJANGO_SETTINGS_MODULE.
  6. Configure MySQL (or PostgreSQL on paid plans), run migrate and collectstatic.
  7. 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:

  1. Click Add a new web app.
  2. Choose Manual configuration (not the Django option; that one generates a new project).
  3. 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()
the WSGI file PythonAnywhere runsproject_home = "/home/myusername/mysite"sys.path.insert(0, project_home)load_dotenv(os.path.join(project_home, ".env"))os.environ["DJANGO_SETTINGS_MODULE"] = "mysite.settings"application= get_wsgi_application()12341Without it, importing mysite fails with ModuleNotFoundError.2Secrets come from .env, not from the web UI or code.3The dotted path, not a file path.4The name the server looks for.This file lives under /var/www, not in your repositorydjango-deployment.com
Edits here only take effect after pressing Reload on the Web tab.

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"
static mappings on the Web tabDirectoryFilled by/static//home/myusername/mysite/staticfilescollectstatic (STATIC_ROOT)/media//home/myusername/mysite/mediauser uploads (MEDIA_ROOT)The mapping serves files directly, without Djangodjango-deployment.com
A 404 on CSS usually means collectstatic has not run since the last change.
python manage.py collectstatic --noinput

On the Web tab, under Static files, add a mapping:

URLDirectory
/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
  • DEBUG is False (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.
three logs, three questionsSymptomRead this log"Something went wrong" pageerror log: tracebackat import or startupSite loads slowly or hangsserver log: workerrestarts and timeoutsA URL returns 404 or 403access log: status andpath of each requestReload seems to do nothingserver log: which WSGI file loadedThe most recent entries are at the bottom of each logdjango-deployment.com
Links to all three logs are on the Web tab, next to the Reload button.

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

  • DEBUG is False and SECRET_KEY comes from .env, not from settings.py in Git.
  • .env has mode 600 and 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 install outside 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 of STATIC_ROOT, so admin assets are missing.
  • Using ALLOWED_HOSTS = ["*"] to silence errors instead of listing the real domain.

Next steps

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.

Keep going