5 min read

One question when starting with Django is “How do I scale my app”? Brandon Rhodes has answered this question in Foundations of Python Network Programming. Rhodes shows us different options, so in this post we will focus on the preferred and main option: Gunicorn + Apache + Nginx.

The idea of this architecture is to have Nginx as a proxy to delegate dynamic content to Gunicorn and static content to Apache. As Django, by itself, does not handle static content, and Apache does it very well, we can take advantages from that. Below we will see how to configure everything.


Project settings

  • STATIC_ROOT: /var/www/myproject/static
  • STATIC_URL: /static/
  • MEDIA_ROOT: /var/www/myproject/media
  • MEDIA_URL: /media/
  • ALLOWED_HOSTS: myproject.com


Gunicorn is a Python and WSGI compatible server, making it our first option when working with Django.

It’s possible to install Gunicorn from pip by running:

pip install gunicorn

To run the Gunicorn server, cd to your project directoryand run:

gunicorn myproject.wsgi:application -b localhost:8000

By default, Gunicorn runs just one worker to serve the pages. If you feel you need more workers, you can start them by passing the number of workers to the –workers option. Gunicorn also runs in the foreground, but you need to configure a service on your server. This, however, is not the focus of this post.

Visit localhost:8000 on your browser and see that your project is working. You will probably see that your static wasn’t accessible. This is because Django itself cannot serve static files, and Gunicorn is not configured to serve them too. Let’s fix that with Apache in the next section.

If your page does not work here, check if you are using a virtualenv and if it is enabled on the Gunicorn running process.


Installing Apache takes some time and is not the focus of this post; additionally, a great majority of the readers will already have Apache, so if you don’t know how to install Apache, follow this guide.

If you already have configured Apache to serve static content, this one will be very similar to what you have done. If you have never done that, do not be afraid; it will be easy!

First of all, change the listening port from Apache. Currently, on Apache2, edit the /etc/apache2/ports.conf and change the line:

Listen 80


Listen 8001

You can choose other ports too; just be sure to adjust the permissions on the static and media files dir to match the current Apache running user needs.

Create a file at /etc/apache2/sites-enabled/myproject.com.conf and add this content:

<VirtualHost *:8001>

ServerName static.myproject.com

ServerAdmin webmaster@localhost

CustomLog ${APACHE_LOG_DIR}/static.myproject.com-access.log combined

ErrorLog ${APACHE_LOG_DIR}/static.myproject.com-error.log

# Possible values include: debug, info, notice, warn, error, crit,

# alert, emerg.

LogLevel warn

DocumentRoot /var/www/myproject 

Alias /static/ /var/www/myproject/static/

Alias /media/ /var/www/myproject/media/

<Directory /var/www/myproject/static>

Require all granted


<Directory /var/www/myproject/media>

Require all granted



Be sure to replace everything needed to fit your project needs. But your project still does not work well because Gunicorn does not know about Apache, and we don’t want it to know anything about that. This is because we will use Nginx, covered in the next session.


Nginx is a very light and powerful web server. It is different from Apache, and it does not spawn a new process for every request, so it works very well as a proxy.

As I’ve said, when installing Apache, you would lead to this reference to know how to install Nginx.

Proxy configuration is very simple in Nginx; just create a file at /etc/nginx/conf.d/myproject.com.conf and put:

upstream dynamic {



upstream static {



server {

   listen 80;

   server_name myproject.com;

   # Root request handler to gunicorn upstream

   location / {

       proxy_pass   http://dynamic;


   # Static request handler to apache upstream

   location /static {

       proxy_pass http://static/static;


   # Media request handler to apache upstream

   location /media {

       proxy_pass http://static/media;


   proxy_set_header X-Real-IP $remote_addr;

   proxy_set_header Host $host;

   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

   error_page   500 502 503 504 /50x.html;


This way, you have everything working on your machine! If you have more than one machine, you can dedicate the machines to deliver static/dynamic contents.

The machine where Nginx runs is the proxy, and it needs to be visible from the Internet. The machines running Apache or Gunicorn can be visible only from your local network. If you follow this architecture, you can just change the Apache and Gunicorn configurations to listen to the default ports, adjust the domain names, and set the Nginx configuration to deliver the connections over the new domains.

Where to go now?

For more details on deploying Gunicorn with Nginx, see the Gunicorn deployment page. You would like to see the Apache configuration page and the Nginx getting started page to have more information about scalability and security.


In this post you saw how to configure Nginx, Apache, and Gunicorn servers to deliver your Django app over a proxy environment, balancing your requests through Apache and Gunicorn. There was a state about how to start more Gunicorn workers and where to find details about scaling each of the servers being used.


[1] – PEP 3333 – WSGI

[2] – Gunicorn Project

[3] – Apache Project

[4] – Nginx Project

[5] – Rhodes, B. & Goerzen, J. (2014). Foundations of Python Network Programming. New York, NY: Apress.

About the author

Jean Jung is a Brazilian developer passionate about technology. Currently a System Analyst at EBANX, an international payment processing cross boarder for Latin America, he’s very interested in Python and Artificial Intelligence, specifically Machine Learning, Compilers, and Operational Systems. As a hobby, he’s always looking for IoT projects with Arduino.

Subscribe to the weekly Packt Hub newsletter

* indicates required


Please enter your comment!
Please enter your name here