Skip to main content

Hosting a Django site on cPanel

If you have a Django website that you need to host on a cPanel-powered provider I would first suggest thinking again. But if you have no choice, here is how I’ve done it.

cPanel is not geared towards hosting Python-based websites and they offer very limited support if you need help. I would suggest looking instead at PythonAnywhere, which I’ve never used but is popular and seems like a good alternative to more modern services like Heroku, Render.com, Fly.io, or to setting up your own Digital Ocean Droplet or similar server.

But if you’re stuck with cPanel, let’s continue. First, here are three other posts that do things slightly differently but which I found useful, especially given cPanel’s lack of decent documentation on this topic:

If you have any comments or corrections about this tutorial, do email me.


§ Assumptions

These instructions assume several things:

  • You already have a cPanel Linux-based server, maybe with an existing site(s) running on it.
  • You already have a Django project running successfully on your own computer.
  • You’re using pip to manage your Python dependencies. (It might be possible to install pipenv, poetry, etc. on your cPanel server but it’s not set up for that; avoid if possible.)
  • The code is already in a git repository. (It doesn’t matter if it’s also at GitHub or elsewhere; we’ll be pushing the code directly from your computer to the server.)
  • Your domain is hosted at the same host as your cPanel server is. Otherwise you’ll need to point the domain to your cPanel host. Both are beyond the scope of this tutorial.
  • Your Django project will use a MySQL database. You could probably use PostgreSQL instead – you’ll just need to alter those steps.

In the instructions below these are things that you’ll need to replace with your own values:

  • example.org – the domain your Django website will be available at.
  • myprimarydomain.org - the “Primary Domain” on your cPanel account. If this is the first site on your account it will be the same as the domain of this Django website.
  • myproject – the name of your Django project. The one you used when creating the project for the first time.
  • myrepo – the name of your Git repository. It might be the same as your Django project name but doesn’t have to be.
  • myhostname – this is up to you, and could be the same as one of the previous values. It’s how we’ll refer to the server for this project.
  • cpanelusername – the username you use to log into cPanel.

§ The steps

1. Check your Python requirements

To serve static files (CSS, JavaScript, etc.) we will use whitenoise. So if you’re not already using it, pip install whitenoise and follow the instructions to set up your Django project to use it.

I originally used mysqlclient to connect to the database but could not get it successfully installed using cPanel, with errors like:

x python setup.py bdist_wheel did not run successfully.
│ exit code: 1
╰─> See above for output.

note: This error originates from a subprocess, and is likely not a problem with pip.
ERROR: Failed building wheel for mysqlclient
error: subprocess-exited-with-error

In the end I had to remove mysqlclient and pip install mysql-connector-python instead.

2. Create a MySQL database

In cPanel create a new MySQL database. Follow Charlie El Awbery’s instructions, which vary depending on whether this is your first database (use the “MySQL Database Wizard”) or not (use “MySQL Databases”).

After this you should have the database name, the user name, and the password: note them down.

3. Create a passenger_wsgi.py file

Add a file named passenger_wsgi.py at the root of your Django project (probably alongside your requirements.txt file) and commit it to git.

import os
import sys
from myproject.wsgi import application

sys.path.insert(0, os.path.dirname(__file__))

environ = os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

You will need to change:

  • myproject.wsgi – this should be the path to the wsgi.py file Django created when you started your project.
  • myproject.settings – this should be the path to the settings module you want the site to use on cPanel. For example if, instead of a settings.py file, you have a settings folder containing a different file for each environment, you might need something like myproject.settings.production

Other tutorials have more complicated passenger_wsgi.py files but this one works for me.

4. Set up SSH Access to the server

You will need SSH Access so that you can push your code to the server using Git. There are variations on how to set it up – here is cPanel’s documentation – but this is how I did it on a Mac:

  1. In cPanel, in “SSH Access”, add a new SSH Key called name_id_rsa, replacing name with your name, e.g. terry_id_rsa (in case you create others for other users).

  2. On your computer, create a .ssh directory (note the leading dot) in your user home directory, if it doesn’t already exist. e.g. /Users/terry/.ssh/

  3. Copy the private and public keys from cPanel into text files at these locations on your computer:

    • ~/.ssh/myhostname_id_rsa - the private key
    • ~/.ssh/myhostname_id_rsa.pub - the public key
  4. I think I had to change the permission of the first one so that only I could read and write it: chmod 600 ~/.ssh/myhostname_id_rsa

  5. Create a text file at ~/.ssh/config if it doesn’t already exist, and add this to it:

    Host myhostname
        HostName myprimarydomain.org
        User cpanelusername
        IdentityFile ~/.ssh/myhostname_id_rsa
        # Add these if you want to store the passphrase in your Apple Keychain:
        UseKeychain yes
        AddKeysToAgent yes
    
  6. You will probably need to open a new tab or window in Terminal but you should now be able to do ssh myhostname to log in to your server.

5. Create a Git repository on the server

In cPanel, in “Git Version Control”, create a new empty Git Repository.

Set the Repository Path to git_repositories/myrepo. This will create the folders in your cPanel home directory, and it’s where your project files will live once we push them to the server.

You could put this folder anywhere, but I like to have it in a folder that’s obviously about Git. It makes it clearer that nothing else should go in here, and you could, if necessary, delete all the files and push a new copy with git.

If you set up other projects on your cPanel server in future (for which you have my sympathy) you can put them in new folders within ~/git_repositories/

6. Push your code to the server

In cPanel, when looking at the “Manage” screen of your new Git repository, copy the “Clone URL” and use that in this command on your computer, to add a new Git remote called production, or whatever else you prefer:

git remote add production <clone URL>

e.g. (all one line):

git remote add production ssh://cpanelusername@myprimarydomain.org/home/cpanelusername/git_repositories/myrepo

If you now type git remote you will see this listed alongside any existing Git remotes (like origin) that you can push code to.

So now, push the main branch of your code (or a different branch if you prefer):

git push production main

7. Ensure the files appear

When I looked in the folder ~/git_repositories/myrepo/ on the server, there were no files there.

The missing step was, in cPanel, in the “Manage” screen of the Git repository, select the main branch (or whichever you want) from the “Checked-Out Branch” drop-down menu, and click “Update”. The files from your main branch should now appear in ~/git_repositories/myrepo/

8. Create the domain

In cPanel, in “Domains”, create a new Domain (example.org) and uncheck the “Share document root” checkbox. You can then “Specify the directory where you want the files for this domain to exist”, which should be ~/git_repositories/myrepo

Back on the “List Domains” screen you can now set “Force HTTPS Redirect” to “On”.

9. Create the Python app

In cPanel, in “Setup Python App”, create a new app with these settings:

NameValue
Python version3.8.6
Application Rootgit_repositories/myrepo
Application URLexample.org
Application Startup filepassenger_wsgi.py
Application Entry Pointapplication
Passenger Log Filelogs/myrepo.log

You can choose a different Python version; 3.8.6 was the most recent available to me.

Click “CREATE”.

10. Add environment variables

On the same page for your Python App, go to the bottom and create these environment variables:

NameValue
ALLOWED_HOSTSexample.org,www.example.org
DJANGO_SECRET_KEY[your secret key]
DJANGO_SETTINGS_MODULEmyproject.settings
DATABASE_URLmysql://databaseuser:databasepassword@localhost:3306/databasename

Then click “SAVE”, at the top right of the page, or these will all be lost (ask me how I know).

Notes:

  • You can create a new Django Secret Key for this site using any secret key generator.
  • The settings module should be the same as the one mentioned in your passenger_wsgi.py file. (Maybe we don’t need both of these?)
  • For the database URL, replace databaseuser, databasepassword and databasename with the ones you noted down in Step 2.

11. Install Python requirements

Click the “Run Pip Install” button and select requirements.txt. All being well it will successfully install your required Python modules in a new virtual environment.

12. Collect static files and migrate the database

On the same page for your Python App, in the “Execute Python Script” field, enter each of these, clicking the “Run Script” button after each one:

  • manage.py collectstatic --no-input
  • manage.py migrate

Note: If you prefer you can run these from the command line once you’re SSH’d into the server. At the top of the Python App page in cPanel is a long command it says you can use to “enter to the Virtual Environment”. Copy this and paste it into your terminal. Then you can run manage.py ... commands from there, now or in the future.

13. Start the app

On the same page for your Python App, click the “START APP” button at the top of the page. Visit your website (www.example.org) in the browser.

When I first did this I got a “Permission Denied” error when it tried to access Passenger.json (or maybe it was Passengerfile.json) – I don’t know what that is. I think I fixed this by changing permissions like so while SSH’d into the server:

chmod ug+rx ~/git_repositories
chmod ug+rx ~/git_repositories/myrepo

(Let me know if you do or don’t get this problem, and how you fix it.)

14. Add error logging

I couldn’t work out if it’s possible to see any Django errors by default using cPanel’s logging. However, if you add this to your Django settings then it will log tracebacks of errors to the file specified:

LOG_FILE = "/home/cpanelusername/logs/myproject-django-errors.log"

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "verbose": {
            "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
            "style": "{",
        },
    },
    "handlers": {
        "file": {
            "level": "ERROR",
            "class": "logging.FileHandler",
            "filename": LOG_FILE,
        },
    },
    "loggers": {
        "django": {
            "handlers": ["file"],
            "level": "ERROR",
            "propagate": True,
        },
    },
}

15. Set up hosting for media files

If your Django website needs to host “media” files – files and images uploaded through the Django Admin, or through other forms on your website – then the simplest way I could find was to set up a separate subdomain from which to serve them. At least cPanel is good at hosting static files.

  1. In cPanel, in “Subdomains”, make a new subdomain media.example.org, with a Document Root of ~/media.example.org

  2. That should have created a directory at that location. Inside it create a text file called .htaccess (note the leading dot) containing this (with your domain name instead):

    <IfModule mod_headers.c>
        Header set Access-Control-Allow-Origin "https://www.example.org"
        Header set Access-Control-Allow-Headers "X-Requested-With"
    </IfModule>
    

    This enables your Django website to access the files hosted on your new subdomain. You might find you need to set other or different headers there, but this has worked for me so far.

  3. In cPanel, in “Domains”, set “Force HTTPS Redirect” to “On” for media.example.org.

  4. In the Django settings file used on the server, set these settings:

    MEDIA_ROOT = "/home/cpanelusername/media.example.org/"
    MEDIA_URL = "https://media.example.org/"
    

    (Don’t forget to commit the changes and push the files.)

You should now find that new media files will be uploaded into ~/media.example.org/ and will be served from https://media.example.org.

You might need to create extra directories within ~/media.example.org/ if you’ve set model fields to upload_to sub-directories. I can’t recall if Django will create them automatically if they’re missing.


§ Ongoing work

You should now have a working website 🤞.

When you make changes to your code you’ll need to follow this procedure to update the site:

  1. Commit your changes to Git and then git push production main (or whatever remote name and branch name you’re using).

  2. If you haven’t changed requirements.txt, added any migrations, or changed any static files you can, in cPanel, in “Setup Python App”, click the circular arrow icon to Restart your app.

  3. Or, if you have done any of those things:

    1. In cPanel, in “Setup Python App”, click the pencil icon next to your app.
    2. If you’ve changed requirements.txt, click the “Run Pip Install” button.
    3. If you’ve added any migrations, go to the “Execute python script” field, enter manage.py migrate, and click the “Run Script” button.
    4. If any static files have been added or changed, go to the “Execute python script” field, enter manage.py collectstatic --no-input, and click the “Run Script” button.
    5. Click the “RESTART” button at the top of the page.

§ Conclusion

Setting up a Django site on cPanel isn’t that much more difficult than many other options, but it’s not clear how to put all the pieces together because cPanel isn’t focused on this kind of website. It can work but it doesn’t feel right in many ways.

I initially wanted to host both “production” and “staging” sites on the same cPanel server, perhaps using different Git branches for each one. This seems like a lot more trouble than it’s worth. See, for example, these forum threads.

It might be possible to have cPanel pull changes from your Git repository on GitHub, rather than pushing them from your computer. See these two documentation pages for example. But I haven’t tried that and it seems like cPanel don’t recommend it.

If you have any comments or suggestions for this guide, please do email me.