Mind Chasers Inc.
Mind Chasers Inc.

Quick Install of Apache, WSGI, and Django on Ubuntu Linux 18.04

A Step-by-Step cookbook to install Apache and Django using WSGI on Ubuntu Linux

X-ray Engineering Services


This article is a brief review of the steps to set up a local Apache HTTP server with Django suitable for local development and testing. We don't address security concerns in this article. Before deploying a web server on a public interface, security topics should be thoroughly reviewed, and these can be found on both the Apache and Django project web sites (see links below).

In case you're not familiar with the open source technologies discussed in this article, a brief introduction of each component is provided below:

  • Ubuntu Linux 18.04 is a recent long-term release for the very popular and stable Linux desktop distribution. This operating system can be downloaded for free at https://www.ubuntu.com and will run on most PCs without too much trouble. Ubuntu 20.04 has also been released this year and is certainly worthy of your consideration.
  • The Apache HTTP Server Project is a popular & powerful Web server for serving up dynamic content across the Web. It may be overkill for personal network installations; however, it can be very useful for developing & testing new technologies locally before deploying to a server (e.g., bare metal) in the cloud.
  • Django is a Web framework for building world-class web sites. The framework uses Python, which is a very friendly & powerful scripting language used in an endless number of applications on local networks and data servers.
  • WSGI (Web Server Gateway Interface) is a connector module that enables communication between the Apache web server and the Django web framework. The default data flow is one where a Web request via a browser is routed to the Apache web server. The server in turn makes a request call into Django via WSGI, which is an Apache module. Django processes the request and returns the response to Apache for transmission back across the Internet to the browser.

We're starting with a clean x86 64-bit Ubuntu 18.04 system with Python 3.6 but with neither Apache nor Django installed:

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.5 LTS
Release:	18.04
Codename:	bionic

note: lsb_release provides "certain LSB (Linux Standard Base) and distribution-specific information".

$ which apache2

$ dpkg -L apache2
dpkg-query: package 'apache2' is not installed

$ python3
Python 3.6.9 (default, Oct  8 2020, 12:12:24) 

>>> import django
ModuleNotFoundError: No module named 'django'

Use Ubuntu's apt (Advanced Packaging Tool) to install our base apache2 installation


$ sudo apt install apache2
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following additional packages will be installed:
  apache2-bin apache2-data apache2-utils libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.2-0
Suggested packages:
  www-browser apache2-doc apache2-suexec-pristine | apache2-suexec-custom
The following NEW packages will be installed:
  apache2 apache2-bin apache2-data apache2-utils libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.2-0
0 upgraded, 9 newly installed, 0 to remove and 0 not upgraded.
Enabling module mpm_event.
Enabling module authz_core.
Enabling module authz_host.
Enabling module authn_core.
Enabling module auth_basic.
Enabling module access_compat.
Enabling module authn_file.
Enabling module authz_user.
Enabling module alias.
Enabling module dir.
Enabling module autoindex.
Enabling module env.
Enabling module mime.
Enabling module negotiation.
Enabling module setenvif.
Enabling module filter.
Enabling module deflate.
Enabling module status.
Enabling module reqtimeout.
Enabling conf charset.
Enabling conf localized-error-pages.
Enabling conf other-vhosts-access-log.
Enabling conf security.
Enabling conf serve-cgi-bin.
Enabling site 000-default.


We want mod-wsgi, which is an Apache module for the implmenentation of python WSGI. This will enable us to run our Django application using Apache and also do cool things like SSE server push via WSGI.

Install mod-wsgi for python 3 making sure to use the Apache library for python3. Information on various Ubuntu HTTP server packages can be found on the Ubuntu package site.

$ sudo apt install libapache2-mod-wsgi-py3
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
apache2_invoke: Enable module wsgi

Note the last line above that states the module was enabled. Although it's not necessary, let's check that it is indeed installed and where it's located:

$ dpkg -S mod_wsgi
libapache2-mod-wsgi-py3: /usr/lib/apache2/modules/mod_wsgi.so
libapache2-mod-wsgi-py3: /usr/lib/apache2/modules/mod_wsgi.so-3.6

Check if our Apache Web Server is running

$ ps -e | grep apache
 3231 ?        00:00:00 apache2
 3234 ?        00:00:00 apache2
 3235 ?        00:00:00 apache2

You should now be able to visit your Linux PC via a browser and see the Apache2 Ubuntu Default Page.

Install Django from source

It's relatively easy to work with Django from source using the Git version control system. We'll install it under our /build tree:

$ mkdir -p /build; cd /build

$ git clone https://github.com/django/django.git
Cloning into 'django'...

Checkout the latest production branch (we could also checkout stable/2.2.x)

$ cd django

$ git tag -l | grep ^3.1

$ git checkout 3.1.3
Note: checking out '3.1.3'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 4264d78f2d [3.1.x] Bumped version for 3.1.3 release.

Create our own development branch so we can make changes to the source, commit them, create patches, etc.

$ git checkout -b dev3.1.3
Switched to a new branch 'dev3.1.3'

Determine where to configure the django library:

$ python3 -c "import site; print(site.getsitepackages())"
['/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages', '/usr/lib/python3.6/dist-packages']

$ cd /usr/local/lib/python3.6/dist-packages

$ sudo sh -c "echo '/build/django' > django.pth"

Note that the last line above creates a path configuration file so python can find our Django library.

Verify it's installed in our python3 environment:

$ python3 -c 'import django; print(django.VERSION)'
(3, 1, 3, 'final', 0)

Now we have Apache running and Django installed so python3 can import it and use it.

Create an example Django test project: tstdj under /build

Before trying to create a new Django project, we may need to take care of some dependencies (i.e., pytz and asgiref). There are multiple ways to do this, and one is shown below:

$ sudo apt install pip3
$ sudo -H pip3 install  pytz asgiref sqlparse

Now let's create a Django project:

$ cd /build
$ python3 /build/django/django/bin/django-admin.py startproject tstdj

Find your settings.py file under tstdj and modify your ALLOWED_HOSTS so it includes your interface (e.g., ""). If you don't do this, you'll see an error when you visit your website explaining what you need to fix (courtesy of the Django developers).

If you want to take a break and verify your Django test project is OK, you can use:

$ cd /build/tstdj

$ python3 manage.py migrate  
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK

$ python3 manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

If you are unfamiliar with runserver and database migrations, head over to djangoproject.com and walk through their very good tutorial.

Configure the Apache server to serve Django

Now it's time to tie it all together and serve up your Django project using Apache and mod-wsgi.

Open two terminals side-by-side to modify Apache conf files and view Apache logs:

First terminal:

$ cd /etc/apache2

$ grep APACHE_LOG_DIR envvars 
export APACHE_LOG_DIR=/var/log/apache2$SUFFIX 

Second terminal, making use of the path determined for APACHE_LOG_DIR above:

$ cd /var/log/apache2
$ ls
access.log  error.log  other_vhosts_access.log

This second terminal will let you view errors in error.log and accesses in access.log.

For completeness sake, make sure mod-wsgi is enabled:

$ sudo a2enmod wsgi
Module wsgi already enabled

In the first terminal open /etc/apache2/apache2.conf with your favorite editor after making a copy of it:

$ sudo cp apache2.conf apache2.conf.orig

Add the following lines to the end of apache2.conf:

WSGIDaemonProcess www-data processes=2 threads=12 python-path=/build/tstdj
WSGIProcessGroup www-data
WSGIRestrictEmbedded On
WSGILazyInitialization On
WSGIScriptAlias / /build/tstdj/tstdj/wsgi.py

<Directory /build/tstdj/tstdj>
    Require all granted

Note that there are many options for configuring, tuning, and tweaking both Apache and mod-wsgi, but these are similar to the ones we use on a production site. Refer to the documentation of both projects for further information.

Now, restart Apache and give it a try. If you have errors, review error.log in /var/log/apache2 or submit a comment below and we'll try our best to help you.

sudo apachectl -k restart

Lastly, make sure it's running:

$ ps -e | grep apache
13172 ?        00:00:00 apache2
13173 ?        00:00:00 apache2
13174 ?        00:00:00 apache2
13175 ?        00:00:00 apache2
13176 ?        00:00:00 apache2

In order to try your installation, open your browser and type in the IP address you set in your Django settings.py file for ALLOWED_HOSTS above. Hopefully, you see something similar to what is shown below.

Django screenshot
Screenshot from browser


Didn't find an answer to your question? Post your issue below or in our new FORUM, and we'll try our best to help you find a solution.

And please note that we update our site daily with new content related to our open source approach to network security and system design. If you would like to be notified about these changes, then please join our mailing list.

Related articles on this site:

subscribe to mailing list:

Date: Jan. 12, 2018

Author: Terry Sankar


Followed your directions : Have : Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request. error.log : [Fri Jan 12 19:44:15.361543 2018] [mpm_event:notice] [pid 30864:tid 140116263982976] AH00491: caught SIGTERM, shutting down [Fri Jan 12 19:44:15.621039 2018] [wsgi:warn] [pid 31010:tid 140105568868224] mod_wsgi: Compiled for Python/3.5.1+. [Fri Jan 12 19:44:15.621101 2018] [wsgi:warn] [pid 31010:tid 140105568868224] mod_wsgi: Runtime using Python/3.5.2. [Fri Jan 12 19:44:15.622229 2018] [mpm_event:notice] [pid 31010:tid 140105568868224] AH00489: Apache/2.4.18 (Ubuntu) mod_wsgi/4.3.0 Python/3.5.2 configured $ [Fri Jan 12 19:44:15.622259 2018] [core:notice] [pid 31010:tid 140105568868224] AH00094: Command line: '/usr/sbin/apache2' So close ! Thanks for you help.

Date: Jan. 19, 2018

Author: Kevin O'Gorman


The instructions seemed clear enough. I created /build/tstdj as myself, but ensured that directories were at least mode 755 and files at least 644. I changed the configuration slightly because I already had apache2 running: so I made WSGIScriptAlias /tstdj/ /build/tstdj/tstdj/wsgi.py so that the WSGI stuff should live side-by-side with the static pages I already have. I supplied my username as <username> in the WSGIDaemonProcess and the WSGIProcessGroup The end result is a permissions error: - - [19/Jan/2018:11:57:46 -0800] "GET /tstdj/ HTTP/1.1" 403 509 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" But my existing pages are okay

Date: Jan. 21, 2018

Author: Kevin O'Gorman


I got a message like the one above, but it was not fatal. What was fatal the first time was that this method of installing Django does not update the timezone information properly. If instead, you use pip to install django==1.11 for instance, it notices the problem and installs package pytz, which solves it. I like this better for other reasons too, such as not needing full pathnames to get to django. The warning in the previous message is actually no problem. wsgi was compiled for 3.5.1+ where the "+" means later versions are okay. So the runtime using the next point release is compatible.

Date: Nov. 10, 2018

Author: VijayKumar


Forbidden You don't have permission to access / on this server. Apache/2.4.29 (Ubuntu) Server at Port 80

Date: Nov. 10, 2018

Author: VijayKumar


[Sat Nov 10 22:55:15.645164 2018] [mpm_event:notice] [pid 13257:tid 139996807568320] AH00491: caught SIGTERM, shutting down [Sat Nov 10 22:55:15.799973 2018] [mpm_event:notice] [pid 13393:tid 140118498139072] AH00489: Apache/2.4.29 (Ubuntu) mod_wsgi/4.5.17 Python/3.6 configured -- resuming normal operations [Sat Nov 10 22:55:15.800089 2018] [core:notice] [pid 13393:tid 140118498139072] AH00094: Command line: '/usr/sbin/apache2'

Date: Nov. 10, 2018

Author: Mind Chasers


The error could be caused by apache not being able to read Django's wsgi.py file. The apache2 daemon is probably running as www-data (confirm with '$ ps aux'), so make sure your django project path to wsgi.py is readable by the daemon. The simplest thing to do just to get things running at first is to set your file permissions to 664, which should be the default when you create a new project ($ chmod 644 <filename>). Hope this helps.

Date: Nov. 10, 2018

Author: VijayKumar


AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using Set the 'ServerName' directive globally to suppress this message

Date: Nov. 11, 2018

Author: Mind Chasers


There are a few different ways to handle this. For example, you can set your FQDN manually in your /etc/apache2/apache2.conf file or run '$ sudo a2enconf fqdn', which will enable your fqdn.conf configuration file.

Date: April 28, 2019

Author: Gary


Can I use www-data as the user name? Can I used the path as /var/www/html/build/tstdj I also use anaconda3 and build my django projects using django-admin.py startproject tstdj will that cause any problems? Than you Gary

Date: April 28, 2019

Author: Mind Chasers


Regarding www-data as the user name, I assume you mean for the WSGIDaemonProcess directive? For the purposes of this article, that should be the default. Thank you for pointing it out. We updated this doc today and replaced <username> with www-data. Regarding moving the Django project under /var/www/html, this isn't recommended. From the mod_wsgi documentation: "If possible you should avoid placing WSGI scripts under the DocumentRoot in order to avoid accidentally revealing their source code if the configuration is ever changed." Plus, you'll have to manage root permissions. You should be able to use the Anaconda distribution without issue. We use it for Windows but not for Linux. And of course it's up to you whether you want to work with Django from source or not.

Date: April 28, 2019

Author: Gary


Thank you for the fast reply. I will make a directory outside of var/www/html. Thank you for the advice. I have never worked with the source but I will give it a try. Again thank you for the help.

Date: Aug. 6, 2019

Author: mahans


Hi My system is ubuntu 18.04 lts it don't find wsgi in 'sudo a2enmod wsgi' and result is 'ERROR: Module wsgi does not exist!' but i install it from your commant befor what is the problem? Thank you mahan

Date: Aug. 6, 2019

Author: Mind Chasers


Please go back in the article and review the following steps: $ sudo apt install libapache2-mod-wsgi-py3 $ dpkg -S mod_wsgi And also run the following: $ ls /etc/apache2/mods-available/ | grep wsgi wsgi.conf wsgi.load If you're still stuck, reply back & we'll try to help you.

Date: Jan. 7, 2020

Author: Saurabh


Hello, I installed wsgi as you mentioned above and also make it enable. It is showing everything ok for install but when I', add below line in my apache2.conf file getting 500 internal server error and domain stop. #WSGIScriptAlias / /var/www/html/server/bt_trucking/wsgi.py #WSGIPythonPath /var/www/html/server:/var/www/html/server/bt_trucking/env/lib/python3.6/site-packages #WSGIDaemonProcess api.haulink.co python-path=/var/www/html/server:/var/www/html/server/bt_trucking/env/lib/python3.6/site-packages #WSGIProcessGroup api.haulink.co Kindly check and let me know for issues. My main target to my client and server application in a single server or virtual machine. I create enable two separate configuration file called client.conf and server.conf and in apache hosts I set up both when I'm testing with HTML file both working, like xyz.com and api.xtz.com, one for the angular app and second for Django project. My Django project successfully running in dev mode but I'm unable to deploy it in production env in apache Linux environment. Kindly suggest. Thanks.

Date: Jan. 8, 2020

Author: Mind Chasers


If you're seeing a 500, then Apache should be writing an error into its log, and this will hopefully give you an idea about what is misconfigured. If you followed the directions on this page, then error.log should be /var/log/apache2/. Feel free to post the error regardless of whether you solve the problem. It might help someone else.

Date: May 2, 2020

Author: Lex


Thank you for great article. I created my project in my home folder rather than the /build approach. It works with runserver but runs into issues with apache. Others are a struggling with the same issue (when not using a virtual environment) and it seems access related: The error.log reports: File "/home/lex/api-project/api/wsgi.py", line 12, in <module> from django.core.wsgi import get_wsgi_application ModuleNotFoundError: No module named 'django' Within python the "import django" works without issue. I would welcome a suggestions on how to solve this properly and not just clobber security. Thanks in advance! Lex

Date: Sept. 6, 2021

Author: vishnudas


how do i get as admin using this blog..i didnt get as admin in django...what username and password should i use in ...

Add a new comment here or reply to one above:

your email address will be kept private
authenticate with a 3rd party for enhanced features, such as image upload
previous month
next month