Tutorial

How To Set Up Django with Postgres, Nginx, and Gunicorn on CentOS 7

How To Set Up Django with Postgres, Nginx, and Gunicorn on CentOS 7
Not using CentOS 7?Choose a different version or distribution.
CentOS 7

Introduction

Django is a powerful web framework that can help you get your Python application or website off the ground. Django includes a simplified development server for testing your code locally, but for anything even slightly production related, a more secure and powerful web server is required.

In this guide, we will demonstrate how to install and configure some components on CentOS 7 to support and serve Django applications. We will be setting up a PostgreSQL database instead of using the default SQLite database. We will configure the Gunicorn application server to interface with our applications. We will then set up Nginx to reverse proxy to Gunicorn, giving us access to its security and performance features to serve our apps.

Prerequisites and Goals

In order to complete this guide, you should have a fresh CentOS 7 server instance with a non-root user with sudo privileges configured. You can learn how to set this up by running through our initial server setup guide.

We will be installing Django within a virtual environment. Installing Django into an environment specific to your project will allow your projects and their requirements to be handled separately.

Once we have our database and application up and running, we will install and configure the Gunicorn application server. This will serve as an interface to our application, translating client requests in HTTP to Python calls that our application can process. We will then set up Nginx in front of Gunicorn to take advantage of its high performance connection handling mechanisms and its easy-to-implement security features.

Let’s get started.

Install the Packages from EPEL and the CentOS Repositories

To begin the process, we’ll download and install all of the items we need from the CentOS repositories. We will also need to use the EPEL repository, which contains extra packages that aren’t included in the main CentOS repositories. Later we will use the Python package manager pip to install some additional components.

First, enable the EPEL repository so that we can get the components we need:

sudo yum install epel-release

With the new repository available, we can install all of the pieces we need in one command:

sudo yum install python-pip python-devel postgresql-server postgresql-devel postgresql-contrib gcc nginx 

This will install pip, the Python package manager. It will also install the PostgreSQL database system and some libraries and other files we will need to interact with it and build off of it. We included the GCC compiler so that pip can build software and we installed Nginx to use as a reverse proxy for our installation.

Set Up PostgreSQL for Django

We’re going to jump right in and set PostgreSQL up for our installation.

Configure and Start PostgreSQL

To start off, we need to initialize the PostgreSQL database. We can do that by typing:

sudo postgresql-setup initdb

After the database has been initialized, we can start the PostgreSQL service by typing:

sudo systemctl start postgresql

With the database started, we actually need to adjust the values in one of the configuration files that has been populated. Use your editor and the sudo command to open the file now:

sudo nano /var/lib/pgsql/data/pg_hba.conf

This file is responsible for configuring authentication methods for the database system. Currently, it is configured to allow connections only when the system user matches the database user. This is okay for local maintenance tasks, but our Django instance will have another user configured with a password.

We can configure this by modifying the two host lines at the bottom of the file. Change the last column (the authentication method) to md5. This will allow password authentication:

. . .

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer
# IPv4 local connections:
#host    all             all             127.0.0.1/32            ident
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
#host    all             all             ::1/128                 ident
host    all             all             ::1/128                 md5

When you are finished, save and close the file.

With our new configuration changes, we need to restart the service. We will also enable PostgreSQL so that it starts automatically at boot:

sudo systemctl restart postgresql
sudo systemctl enable postgresql

Create the PostgreSQL Database and User

Now that we have PostgreSQL up and running the way we want it to, we can create a database and database user for our Django application.

To work with Postgres locally, it is best to change to the postgres system user temporarily. Do that now by typing:

sudo su - postgres

When operating as the postgres user, you can log right into a PostgreSQL interactive session with no further authentication. This is due to the line we didn’t change in the pg_hba.conf file:

psql

You will be given a PostgreSQL prompt where we can set up our requirements.

First, create a database for your project:

CREATE DATABASE myproject;

Every command must end with a semi-colon, so check that your command ends with one if you are experiencing issues.

Next, create a database user for our project. Make sure to select a secure password:

CREATE USER myprojectuser WITH PASSWORD 'password';

Now, we can give our new user access to administer our new database:

GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;

When you are finished, exit out of the PostgreSQL prompt by typing:

\q

Now, exit out of the postgres user’s shell session to get back to your normal user’s shell session by typing:

exit

Create a Python Virtual Environment for your Project

Now that we have our database ready, we can begin getting the rest of our project requirements ready. We will be installing our Python requirements within a virtual environment for easier management.

To do this, we first need access to the virtualenv command. We can install this with pip:

sudo pip install virtualenv

With virtualenv installed, we can start forming our project. Create a directory where you wish to keep your project and move into the directory afterwards:

mkdir ~/myproject
cd ~/myproject

Within the project directory, create a Python virtual environment by typing:

virtualenv myprojectenv

This will create a directory called myprojectenv within your myproject directory. Inside, it will install a local version of Python and a local version of pip. We can use this to install and configure an isolated Python environment for our project.

Before we install our project’s Python requirements, we need to activate the virtual environment. You can do that by typing:

source myprojectenv/bin/activate

Your prompt should change to indicate that you are now operating within a Python virtual environment. It will look something like this: (myprojectenv)user@host:~/myproject$.

With your virtual environment active, install Django, Gunicorn, and the psycopg2 PostgreSQL adaptor with the local instance of pip:

pip install django gunicorn psycopg2

Create and Configure a New Django Project

With our Python components installed, we can create the actual Django project files.

Create the Django Project

Since we already have a project directory, we will tell Django to install the files here. It will create a second level directory with the actual code, which is normal, and place a management script in this directory. The key to this is the dot at the end that tells Django to create the files in the current directory:

django-admin.py startproject myproject .

Adjust the Project Settings

The first thing we should do with our newly created project files is adjust the settings. Open the settings file in your text editor:

nano myproject/settings.py

Start by finding the section that configures database access. It will start with DATABASES. The configuration in the file is for a SQLite database. We already created a PostgreSQL database for our project, so we need to adjust the settings.

Change the settings with your PostgreSQL database information. We tell Django to use the psycopg2 adaptor we installed with pip. We need to give the database name, the database username, the database username’s password, and then specify that the database is located on the local computer. You can leave the PORT setting as an empty string:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'myproject',
        'USER': 'myprojectuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Next, move down to the bottom of the file and add a setting indicating where the static files should be placed. This is necessary so that Nginx can handle requests for these items. The following line tells Django to place them in a directory called static in the base project directory:

STATIC_ROOT = os.path.join(BASE_DIR, "static/")

Save and close the file when you are finished.

Complete Initial Project Setup

Now, we can migrate the initial database schema to our PostgreSQL database using the management script:

cd ~/myproject
./manage.py makemigrations
./manage.py migrate

Create an administrative user for the project by typing:

./manage.py createsuperuser

You will have to select a username, provide an email address, and choose and confirm a password.

We can collect all of the static content into the directory location we configured by typing:

./manage.py collectstatic

You will have to confirm the operation. The static files will then be placed in a directory called static within your project directory.

Finally, you can test your project by starting up the Django development server with this command:

./manage.py runserver 0.0.0.0:8000

In your web browser, visit your server’s domain name or IP address followed by :8000:

http://server_domain_or_IP:8000

You should see the default Django index page:

Django index page

If you append /admin to the end of the URL in the address bar, you will be prompted for the administrative username and password you created with the createsuperuser command:

Django admin login

After authenticating, you can access the default Django admin interface:

Django admin interface

When you are finished exploring, hit CTRL-C in the terminal window to shut down the development server.

Testing Gunicorn’s Ability to Serve the Project

The last thing we want to do before leaving our virtual environment is test Gunicorn to make sure that it can serve the application. We can do this easily by typing:

cd ~/myproject
gunicorn --bind 0.0.0.0:8000 myproject.wsgi:application

This will start Gunicorn on the same interface that the Django development server was running on. You can go back and test the app again. Note that the admin interface will not have any of the styling applied since Gunicorn does not know about the static content responsible for this.

We passed Gunicorn a module by specifying the relative directory path to Django’s wsgi.py file, which is the entry point to our application, using Python’s module syntax. Inside of this file, a function called application is defined, which is used to communicate with the application. To learn more about the WSGI specification, click here.

When you are finished testing, hit CTRL-C in the terminal window to stop Gunicorn.

We’re now finished configuring our Django application. We can back out of our virtual environment by typing:

deactivate

Create a Gunicorn Systemd Service File

We have tested that Gunicorn can interact with our Django application, but we should implement a more robust way of starting and stopping the application server. To accomplish this, we’ll make a Systemd service file.

Create and open a Systemd service file for Gunicorn with sudo privileges in your text editor:

sudo nano /etc/systemd/system/gunicorn.service

Start with the [Unit] section, which is used to specify metadata and dependencies. We’ll put a description of our service here and tell the init system to only start this after the networking target has been reached:

[Unit]
Description=gunicorn daemon
After=network.target

Next, we’ll open up the [Service] section. We’ll specify the user and group that we want to process to run under. We will give our regular user account ownership of the process since it owns all of the relevant files. We’ll give the Nginx user group ownership so that it can communicate easily with Gunicorn.

We’ll then map out the working directory and specify the command to use to start the service. In this case, we’ll have to specify the full path to the Gunicorn executable, which is installed within our virtual environment. We will bind it to a Unix socket within the project directory since Nginx is installed on the same computer. This is safer and faster than using a network port. We can also specify any optional Gunicorn tweaks here. For example, we specified 3 worker processes in this case:

[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=user
Group=nginx
WorkingDirectory=/home/user/myproject
ExecStart=/home/user/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:/home/user/myproject/myproject.sock myproject.wsgi:application

Finally, we’ll add an [Install] section. This will tell Systemd what to link this service to if we enable it to start at boot. We want this service to start when the regular multi-user system is up and running:

[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=user
Group=nginx
WorkingDirectory=/home/user/myproject
ExecStart=/home/user/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:/home/user/myproject/myproject.sock myproject.wsgi:application

[Install]
WantedBy=multi-user.target

With that, our Systemd service file is complete. Save and close it now.

We can now start the Gunicorn service we created and enable it so that it starts at boot:

sudo systemctl start gunicorn
sudo systemctl enable gunicorn

Configure Nginx to Proxy Pass to Gunicorn

Now that Gunicorn is set up, we need to configure Nginx to pass traffic to the process.

Modify the Nginx Configuration File

We can go ahead and modify the server block configuration by editing the main Nginx configuration file:

sudo nano /etc/nginx/nginx.conf

Inside, open up a new server block just above the server block that is already present:

http {
    . . .

    include /etc/nginx/conf.d/*.conf;

    server {
    }

    server {
        listen 80 default_server;

        . . .

We will put all of the configuration for our Django application inside of this new block. We will start by specifying that this block should listen on the normal port 80 and that it should respond to our server’s domain name or IP address:

server {
    listen 80;
    server_name server_domain_or_IP;
}

Next, we will tell Nginx to ignore any problems with finding a favicon. We will also tell it where to find the static assets that we collected in our ~/myproject/static directory. All of these files have a standard URI prefix of “/static”, so we can create a location block to match those requests:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/user/myproject;
    }
}

Finally, we’ll create a location / {} block to match all other requests. Inside of this location, we’ll set some standard proxying HTTP headers so that Gunicorn can have some information about the remote client connection. We will then pass the traffic to the socket we specified in our Gunicorn Systemd unit file:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/user/myproject;
    }

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/home/user/myproject/myproject.sock;
    }
}

Save and close the file when you are finished.

Adjust Group Membership and Permissions

The nginx user must have access to our application directory so that it can serve static files, access the socket files, etc. CentOS locks down each user’s home directory very restrictively, so we will add the nginx user to our user’s group so that we can then open up the minimum permissions necessary to get this to function.

Add the nginx user to your group with the following command. Substitute your own username for the user in the command:

sudo usermod -a -G user nginx

Now, we can give our user group execute permissions on our home directory. This will allow the Nginx process to enter and access content within:

chmod 710 /home/user

With the permissions set up, we can test our Nginx configuration file for syntax errors:

sudo nginx -t

If no errors are present, restart the Nginx service by typing:

sudo systemctl start nginx

Tell the init system to start the Nginx server at boot by typing:

sudo systemctl enable nginx

You should now have access to your Django application in your browser over your server’s domain name or IP address without specifying a port.

Conclusion

In this guide, we’ve set up a Django project in its own virtual environment. We’ve configured Gunicorn to translate client requests so that Django can handle them. Afterwards, we set up Nginx to act as a reverse proxy to handle client connections and serve the correct project depending on the client request.

Django makes creating projects and applications simple by providing many of the common pieces, allowing you to focus on the unique elements. By leveraging the general tool chain described in this article, you can easily serve the applications you create from a single server.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Justin Ellingwood
Justin Ellingwood
See author profile

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
55 Comments
Leave a comment...

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Hi. Nice article! But Nginx config should be kept in /etc/nginx/sites-available and linked to /etc/nginx/sites-enabled directory. This example is fine if you serve only one site with Nginx, but for more sites it can get too complicated.

Justin Ellingwood
DigitalOcean Employee
DigitalOcean Employee badge
March 25, 2015

@korkoszrafal: Thanks for the comment! The sites-available and sites-enabled structure is a convention that the Debian/Ubuntu packaging team implements. It is by no means standard when working on other distributions.

While I do find that using that structure does simplify management, it’s really only a personal choice. Since neither the upstream Nginx project nor the CentOS packagers felt the need to implement that structure, I didn’t feel that it was appropriate to tell users to modify their Nginx configuration structure for the sake of this article.

Hope that at least explains the rational, regardless of whether you agree with the choice :).

This comment has been deleted

    @jellingwood Oh, wrong assumption on my side :) Thanks for explaining.

    Justin Ellingwood
    DigitalOcean Employee
    DigitalOcean Employee badge
    March 25, 2015

    No problem at all! Thanks for taking the time to read and comment.

    This comment has been deleted

      I’ve completed all tutorial and everything worked until nginx configuration. I’ve done everything here said but still do not see django page if I access server address without port

      I ran into this issue as well. I had a typo in my gunicorn conf file for the location of the sock file. Once I fixed this I just needed to reload the daemon and then I was good to go. Hopefully, this will help someone.

      Getting 502 Bad Gateway.

      When I do: sudo cat /var/log/audit/audit.log | grep nginx | grep denied I get: type=AVC msg=audit(1436708616.988:945): avc: denied { write } for pid=2866 comm=“nginx” name=“myprojecthere.sock” dev=“dm-0” ino=11373 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:user_home_t:s0 tclass=sock_file

      When I do: sudo more /var/log/nginx/error.log I get: crit] 2866#0: *1 connect() to unix:/[mypath]/myprojecthere.sock failed (13: Permission denied) while connecting to upstream, client: [clientip], server: [myip], request: “GET / HTTP/1.1”, upstream: “http://uni x:/[mypath]/fcm.sock:/”, host: “[myip]”

      I type:getenforce and see: Enforcing

      how to fix? sorry, n00b, and thanks for this article, huge help

      I was just at the same point as you a few minutes ago. After hitting a wall checking permissions and configs, it dawned on me that it may be selinux. I issued “setenforce permissive” and it now works. So, it is apparently an selinux issue. Note quite sure how to fix it yet without disabling selinux all together, but if I figure it out, I’ll reply again…unless someone beats me to it.

      –Jack

      @jackd942 I made it further in solving this without having to disable selinux. You might want to check out my post at Server Fault. At one point I nuked my box and started over again, so my box at the point it’s on through my S.F. post might not have come from the state it is when I posted the message here.

      You can install audit2allow via:

      yum install /usr/bin/audit2allow
      

      and check out How to use selinux on Centos 6.5 for usage

      Me too…I was just logging back on here to reply with a link…SELinux policy for nginx. audit2allow did the trick…

      OMG, I can’t believe this worked! Everything automated, following the guide, it’s quite incredible.

      Hello and thank your for your tutorial, but i have some problem.

      I’m gettings “The requested URL / was not found on this server” error on my domain. But, for example. domain.com/admin works.

      Do you have any idea, why it can be so?

      Hi. very good, one question. My server is a virtual machine (vmware) and can only access the application within the virtual machine and not from outside.

      Hello all,

      If you got a 502 Bad Gateway error when accessing your site after completing this tutorial, and the error message in nginx’s error log reads " (13: Permission denied) while connecting to upstream", the issue could be the “user” variable in nginx.conf, i.e. the user you are using to access the socket files, etc.

      By default, the “user” in nginx.conf is set to “www-data”, however, in this tutorial, the “user” is assumed to be “nginx”. If “nginx” is added to your group, when you really should have added “www-data”, the result is a Permission denied error, since you didn’t grant www-data, but nginx instead, the access to the socket files, etc

      There are 2 ways to solve this:

      1. If you followed the tutorial and added “nginx” to your group, simply change the “user” in nginx.conf from “www-data” to “nginx”. Then restart nginx (full stop and start if you prefer: ‘sudo service nginx stop’, then ‘sudo service nginx start’)

      2. if you don’t want to change the ‘user’ in nginx.conf, simply add “www-data” user to your group (‘sudo usermod -aG user www-data’), then restart nginx

      Hope this helps.

      You try grant permission for “project name”.sock to 170, Nginx use group permisson. ex: chmod 170 myproject.sock.

      Hi Thuong,

      In the “nginx.config” file, you need to change the “user” variable’s value from “www-data” to “nginx”. Then restart nginx.

      Good luck

      Hi to all, just finished to configure my server, also had problems with 502 error the best way to understand what is going on is to add these code to ExecStart:

      ExecStart=/path/to/your/env/bin/gunicorn --name project_name --workers 4 --max-requests 10 --user user --group nginx --log-level debug --error-logfile /path/to/uour/log/error.log  --bind unix:/path/to/your/socketdir/project_name.socket project_name.wsgi:application
      

      then do

      sudo systemctl daemon-reload
      sudo systemctl restart gunicorn
      
      

      this will show you in logfile what is wrong. Happy installing )

      I’m running centos 7.2 and had the same 502 errors as mentioned. My fix required two changes.

      First, I needed to add the project name, group, and user to the Execstart in /etc/systemd/system/gunicorn.service –name project_name --user user --group nginx

      Second selinux was giving me a permission denied error, so I had to allow it to be able to connect by typing: setsebool httpd_can_network_connect on -P

      Hopefully this helps anyone frustrated by the 502 error.

      Hi, Thank for a great article, however I wasn’t able to get nginx to forward on port 80. After spending a very long time looking for other errors in the configuration I just tried changing the port in nginx.conf to 8080 and it worked.

      Was I meant to delete the other server definition in the nginx.conf?

      I don’t understand why this tutorials was approved; most of tutorial of @jellingwood simply do half of the job. Like this one and this one

      Everything fine until you arrive at the middle (gunicorn) and for both (centos or ubuntu). Hopefully, nice comment with real answers are posted (like @clydesyndrome)

      But Finaly I was able to make it with this gunicorn configuration example. https://gist.githubusercontent.com/marcanuy/5ae0e0ef5976aa4a10a7/raw/986d734541539da488c040279e5e551f2e644458/gunicorn-SITENAME-staging.example.com.service

      I got this error when try to install psycopg2

       error: command 'gcc' failed with exit status 1
      

      502 Bad Gateway Any help ?

      For all of those having 502 Bad Gateway errors… I struggled with this and found a solution:

      http://stackoverflow.com/questions/42310894/502-bad-gateway-with-nginx-gunicorn-django/42490422#42490422

      This tutorial does not work if you are trying to get Django settings from the environment.

      Did you figure out what needs to change in order to set it up to work with environment variables?

      Yes.

      1. Create a file with all the environment variable settings.
      2. Add this line to your gunicorn.service file under the [Service] section:
      EnvironmentFile=<path to the file>
      

      Worked great thanks

      Great article that still works. I just went through it and minus a few typos on my side everything worked well. Thanks

      Great tutorial. selinux and firewall was a blocker for me. I am on a local virtual box to develop against, so I just disabled the firewall and selinux all together.

      Hi to all, just finished to configure my server, also had problems with 502 error the best way to understand what is going on is to add these code to ExecStart:

      ExecStart=/path/to/your/env/bin/gunicorn --name project_name --workers 4 --max-requests 10 --user user --group nginx --log-level debug --error-logfile /path/to/uour/log/error.log  --bind unix:/path/to/your/socketdir/project_name.socket project_name.wsgi:application
      

      then do

      sudo systemctl daemon-reload
      sudo systemctl restart gunicorn
      
      

      this will show you in logfile what is wrong. Happy installing )

      I’m running centos 7.2 and had the same 502 errors as mentioned. My fix required two changes.

      First, I needed to add the project name, group, and user to the Execstart in /etc/systemd/system/gunicorn.service –name project_name --user user --group nginx

      Second selinux was giving me a permission denied error, so I had to allow it to be able to connect by typing: setsebool httpd_can_network_connect on -P

      Hopefully this helps anyone frustrated by the 502 error.

      Hi, Thank for a great article, however I wasn’t able to get nginx to forward on port 80. After spending a very long time looking for other errors in the configuration I just tried changing the port in nginx.conf to 8080 and it worked.

      Was I meant to delete the other server definition in the nginx.conf?

      I don’t understand why this tutorials was approved; most of tutorial of @jellingwood simply do half of the job. Like this one and this one

      Everything fine until you arrive at the middle (gunicorn) and for both (centos or ubuntu). Hopefully, nice comment with real answers are posted (like @clydesyndrome)

      But Finaly I was able to make it with this gunicorn configuration example. https://gist.githubusercontent.com/marcanuy/5ae0e0ef5976aa4a10a7/raw/986d734541539da488c040279e5e551f2e644458/gunicorn-SITENAME-staging.example.com.service

      I got this error when try to install psycopg2

       error: command 'gcc' failed with exit status 1
      

      502 Bad Gateway Any help ?

      For all of those having 502 Bad Gateway errors… I struggled with this and found a solution:

      http://stackoverflow.com/questions/42310894/502-bad-gateway-with-nginx-gunicorn-django/42490422#42490422

      This tutorial does not work if you are trying to get Django settings from the environment.

      Did you figure out what needs to change in order to set it up to work with environment variables?

      Yes.

      1. Create a file with all the environment variable settings.
      2. Add this line to your gunicorn.service file under the [Service] section:
      EnvironmentFile=<path to the file>
      

      Worked great thanks

      Great article that still works. I just went through it and minus a few typos on my side everything worked well. Thanks

      Great tutorial. selinux and firewall was a blocker for me. I am on a local virtual box to develop against, so I just disabled the firewall and selinux all together.

      Hi to all, just finished to configure my server, also had problems with 502 error the best way to understand what is going on is to add these code to ExecStart:

      ExecStart=/path/to/your/env/bin/gunicorn --name project_name --workers 4 --max-requests 10 --user user --group nginx --log-level debug --error-logfile /path/to/uour/log/error.log  --bind unix:/path/to/your/socketdir/project_name.socket project_name.wsgi:application
      

      then do

      sudo systemctl daemon-reload
      sudo systemctl restart gunicorn
      
      

      this will show you in logfile what is wrong. Happy installing )

      I’m running centos 7.2 and had the same 502 errors as mentioned. My fix required two changes.

      First, I needed to add the project name, group, and user to the Execstart in /etc/systemd/system/gunicorn.service –name project_name --user user --group nginx

      Second selinux was giving me a permission denied error, so I had to allow it to be able to connect by typing: setsebool httpd_can_network_connect on -P

      Hopefully this helps anyone frustrated by the 502 error.

      Hi, Thank for a great article, however I wasn’t able to get nginx to forward on port 80. After spending a very long time looking for other errors in the configuration I just tried changing the port in nginx.conf to 8080 and it worked.

      Was I meant to delete the other server definition in the nginx.conf?

      I don’t understand why this tutorials was approved; most of tutorial of @jellingwood simply do half of the job. Like this one and this one

      Everything fine until you arrive at the middle (gunicorn) and for both (centos or ubuntu). Hopefully, nice comment with real answers are posted (like @clydesyndrome)

      But Finaly I was able to make it with this gunicorn configuration example. https://gist.githubusercontent.com/marcanuy/5ae0e0ef5976aa4a10a7/raw/986d734541539da488c040279e5e551f2e644458/gunicorn-SITENAME-staging.example.com.service

      I got this error when try to install psycopg2

       error: command 'gcc' failed with exit status 1
      

      502 Bad Gateway Any help ?

      For all of those having 502 Bad Gateway errors… I struggled with this and found a solution:

      http://stackoverflow.com/questions/42310894/502-bad-gateway-with-nginx-gunicorn-django/42490422#42490422

      This tutorial does not work if you are trying to get Django settings from the environment.

      Did you figure out what needs to change in order to set it up to work with environment variables?

      Yes.

      1. Create a file with all the environment variable settings.
      2. Add this line to your gunicorn.service file under the [Service] section:
      EnvironmentFile=<path to the file>
      

      Great article that still works. I just went through it and minus a few typos on my side everything worked well. Thanks

      Great tutorial. selinux and firewall was a blocker for me. I am on a local virtual box to develop against, so I just disabled the firewall and selinux all together.

      Hi , thanks for this good tutorial

      at the end , when i try : sudo nginx -t

      it gives me the error : nginx: [warn] conflicting server name “*******” on 0.0.0.0:80, ignored

      what can i do ?

      regards

      hi all , this problem is solved

      but i have a new one :

      i am here:

      sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful

      there is no problem , but the site stills dont showing.

      nice tut works perfect for me , thanks

      HI.

      I also had 502 error.

      Looks like gunicorn was installed only in /usr/bin/gunicorn. So I just copy /usr/bin/gunicorn in /home/user/project/project-env/bin/.

      502 error. Fix: sudo chown -R user:nginx myproject/ Python 3.6, Django 2.0.7

      It has been 8 hours a day after 9 hours at office and almost a month since i am trying to get this gunicorn popcorn crap and it has so far not worked. I have a list of online tutorials that i have followed so far and it has grown beyond 20. I have done and followed these tutorials more than 4-5 times. I have so far tried ubuntu, centos 6/7 but no go. I so regret my decision of learning django for my project. I Should have learnt proven old lamp stack or may be the new node.js. I now know why nobody has made video tutorials on this (only one indian girls video is there on youtube with clear instructions); because it simply doesnt work.

      Hello – great article! This saved me many hours pouring over documentation :) I did run into an issue in production where, during a reboot, the gunicorn service would start quicker than the postgresql daemon… and as such, gunicorn and the Django app were left in a broken state (reporting failure since db was not ready).

      Adding “Restart=on-failure” to the gunicorn.service file will enable gunicorn to automatically restart if there is any failure. See more info here: https://www.freedesktop.org/software/systemd/man/systemd.service.html

      I would suggest adding this to your article so that other users can benefit from it. Thanks again for your work!

      Follow the tutorial exactly. But I get a “502 Bad Gateway” error that may be going on?

      Follow the tutorial exactly. But I get a “502 Bad Gateway” error that may be going on?

      Great tutorial !!!

      I would like to know how I do if I want to host more than one application?

      Someone document or tutorial ?

      hugs

      I had some trouble getting the default Django index page to show up in the browser, kept getting err_connection_refused.

      Turns out my firewall was configured to block connections over port 8000, forgot that I had configured it to block most ports in the initial server setup. So running the following command fixed it:

      sudo firewall-cmd --add-port=8000/tcp

      Hopefully this will help anyone else who is having this issue.

      Also ended up running into problems with selinux causing a 502 gateway error when trying to display the page in the final step. Setting selinux to permissive fixed this issue.

      I have problem when starting gunicorn.

      gunicorn --bind 0.0.0.0:8000 core.wsgi:application
      [2019-03-26 10:55:16 +0000] [8560] [INFO] Starting gunicorn 19.9.0
      [2019-03-26 10:55:16 +0000] [8560] [INFO] Listening at: http://0.0.0.0:8000 (8560)
      [2019-03-26 10:55:16 +0000] [8560] [INFO] Using worker: sync
      [2019-03-26 10:55:16 +0000] [8565] [INFO] Booting worker with pid: 8565
      [2019-03-26 10:55:16 +0000] [8565] [ERROR] Exception in worker process
      Traceback (most recent call last):
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/gunicorn/arbiter.py", line 583, in spawn_worker                                                                                                        
          worker.init_process()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/gunicorn/workers/base.py", line 129, in init_process                                                                                                   
          self.load_wsgi()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/gunicorn/workers/base.py", line 138, in load_wsgi                                                                                                      
          self.wsgi = self.app.wsgi()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/gunicorn/app/base.py", line 67, in wsgi
          self.callable = self.load()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 52, in load
          return self.load_wsgiapp()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 41, in load_wsgiapp                                                                                                     
          return util.import_app(self.app_uri)
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/gunicorn/util.py", line 350, in import_app
          __import__(module)
        File "/home/rofi/demo.protek/core/core/wsgi.py", line 16, in <module>
          application = WSGIHandler()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/core/handlers/wsgi.py", line 151, in __init__                                                                                                   
          self.load_middleware()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/core/handlers/base.py", line 80, in load_middleware                                                                                             
          middleware = import_string(middleware_path)
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/utils/module_loading.py", line 20, in import_string                                                                                             
          module = import_module(module_path)
        File "/usr/lib64/python2.7/importlib/__init__.py", line 37, in import_module
          __import__(name)
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/contrib/auth/middleware.py", line 4, in <module>                                                                                                
          from django.contrib.auth.backends import RemoteUserBackend
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/contrib/auth/backends.py", line 4, in <module>                                                                                                  
          from django.contrib.auth.models import Permission
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/contrib/auth/models.py", line 4, in <module>                                                                                                    
          from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/contrib/auth/base_user.py", line 52, in <module>                                                                                                
          class AbstractBaseUser(models.Model):
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/db/models/base.py", line 110, in __new__                                                                                                        
          app_config = apps.get_containing_app_config(module)
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/apps/registry.py", line 247, in get_containing_app_config                                                                                       
          self.check_apps_ready()
        File "/home/rofi/demo.protek/lib/python2.7/site-packages/django/apps/registry.py", line 125, in check_apps_ready                                                                                                
          raise AppRegistryNotReady("Apps aren't loaded yet.")
      AppRegistryNotReady: Apps aren't loaded yet.
      [2019-03-26 10:55:16 +0000] [8565] [INFO] Worker exiting (pid: 8565)
      [2019-03-26 10:55:16 +0000] [8560] [INFO] Shutting down: Master
      [2019-03-26 10:55:16 +0000] [8560] [INFO] Reason: Worker failed to boot.
      

      Hello All,

      I am trying to learn Python, Gunicorn, Nginx & Postgres. I followed this tutorial step by step. But I am getting 502 error while accessing the application from another computer over LAN.

      I spend 2 days to resolve 503 error but didn’t get success. I don’t know where I am doing wrong. I tried suggestions provided in the above comment and still a facinig issue. Please help me

      File as below

      (.MYPROJECTENV) [dinesh@localdomain ~]$ ll -al
      total 24
      drwx--x---. 5 dinesh dinesh  151 Apr 18 11:39 .
      drwxr-xr-x. 4 root   root     36 Apr 17 18:02 ..
      drwxrwxr-x. 4 dinesh nginx    76 Apr 18 19:30 myproject
      drwxrwxr-x. 5 dinesh nginx    74 Apr 17 19:31 .MYPROJECTENV
      
      1. Gunicorn config:
      [Unit]
      Description=gunicorn daemon
      After=network.target
      
      [Service]
      User=dinesh
      Group=nginx
      WorkingDirectory=/home/dinesh/myproject
      Environment="PATH=/home/dinesh/.MYPROJECTENV/bin"
      
      # Old
      #ExecStart=/home/dinesh/.MYPROJECTENV/bin/gunicorn --workers 3 --bind unix:/home/dinesh/myproject/myproject.sock -m 007 myproject.wsgi:application
      
      # New
      ExecStart=/home/dinesh/.MYPROJECTENV/bin/gunicorn --workers 3 --bind http://127.0.0.1 -m 007 myproject.wsgi
      
      
      [Install]
      WantedBy=multi-user.target
      
      
      1. nginx.conf
      
      user dinesh; # nginx;
      worker_processes  1;
      
      error_log  /var/log/nginx/error.log warn;
      pid        /var/run/nginx.pid;
      
      
      events {
          worker_connections  1024;
      }
      
      
      http {
          include       /etc/nginx/mime.types;
          default_type  application/octet-stream;
      
          log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                            '$status $body_bytes_sent "$http_referer" '
                            '"$http_user_agent" "$http_x_forwarded_for"';
      
          access_log  /var/log/nginx/access.log  main;
      
          sendfile        on;
          #tcp_nopush     on;
      
          keepalive_timeout  65;
      
          #gzip  on;
      
          include /etc/nginx/conf.d/*.conf;
          server {
          listen 80;
          server_name 192.168.2.140;
      
          location = /favicon.ico { access_log off; log_not_found off; }
          location /static/ {
              root /home/dinesh/myproject;
          }
      
          location / {
              proxy_set_header Host $http_host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $scheme;
              #proxy_pass http://unix:/home/dinesh/myproject/myproject.sock;
      	proxy_pass http://127.0.0.1:8000;
      
          }
          }
      }
      
      

      After struggling too much, finally, I found the solution for ‘502 Bad Gateway’. The solution as below:

      1. I tried to use IP and port instead of .sock file. For this purpose reference as here

      2. After that see the gunicorn logs as sudo journalctl -u gunicorn

      Try to sort out the issues.
      

      I am getting below error : Apr 26 20:26:07 localdomain.centos gunicorn[9266]: Error: Error: '/var/log/nginx/gunicorn/error.log' isn't writable [FileNotFoundError(2, Apr 26 20:26:07 localdomain.centos systemd[1]: gunicorn.service: main process exited, code=exited, status=1/FAILURE Apr 26 20:26:07 localdomain.centos systemd[1]: Unit gunicorn.service entered failed state. Apr 26 20:26:07 localdomain.centos systemd[1]: gunicorn.service failed. Apr 26 20:27:35 localdomain.centos systemd[1]: Started gunicorn daemon.

      Now my files as below

      1. Nginx File(/ect/nginx/nginx.conf):
      user  dinesh;
      worker_processes  1;
      
      error_log  /var/log/nginx/error.log warn;
      pid        /var/run/nginx.pid;
      
      
      events {
          worker_connections  1024;
      }
      
      
      http {
          include       /etc/nginx/mime.types;
          default_type  application/octet-stream;
      
          log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                            '$status $body_bytes_sent "$http_referer" '
                            '"$http_user_agent" "$http_x_forwarded_for"';
      
          access_log  /var/log/nginx/access.log  main;
      
          sendfile        on;
          #tcp_nopush     on;
      
          keepalive_timeout  65;
      
          #gzip  on;
      
          include /etc/nginx/conf.d/*.conf;
          # Tomcat configuration
          server {
              listen 80;
              server_name 192.168.2.141;
              location /sample {
                  proxy_set_header X-Forwarded-Host $host;
                  proxy_set_header X-Forwarded-Server $host;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_pass http://127.0.0.1:8080/sample;
              }
              # Gunicorn configuration
              location = /favicon.ico { access_log off; log_not_found off; }
              location /static/ {
                  root /home/dinesh/myproject;
              }
      
              location /myproject {
                  proxy_set_header Host $http_host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto $scheme;
                  #proxy_pass http://unix:/home/dinesh/myproject/myproject.sock;
                  proxy_pass http://127.0.0.1:8000/;
              }
          }
      }
      
      1. Gunicorn file(/etc/systemd/system/gunicorn.service):
      [Unit]
      Description=gunicorn daemon
      After=network.target
      
      [Service]
      User=dinesh
      Group=nginx
      WorkingDirectory=/home/dinesh/myproject
      Environment="PATH=/home/dinesh/.MYPROJECTENV/bin"
      
      # Old
      #ExecStart=/home/dinesh/.MYPROJECTENV/bin/gunicorn --workers 3 --bind unix:/home/dinesh/myproject/myproject.sock -m 007 myproject.wsgi:application
      
      # New
      ExecStart=/home/dinesh/.MYPROJECTENV/bin/gunicorn --access-logfile /tmp/gunicorn_access.log --error-logfile /tmp/gunicorn_error.log --workers 3 --bind 0.0.0.0:8000 -m 007 myproject.wsgi
      
      
      [Install]
      WantedBy=multi-user.target
      

      Works well, little out dated but still works with some changes.

      Thanks

      Great article! Worked well for me, until I had to reboot my remote box. Could one of you have a look in to my question, and share your thoughts please?

      Python Nginx Django PostgreSQL ERR 404 NOT FOUND

      Try DigitalOcean for free

      Click below to sign up and get $200 of credit to try our products over 60 days!

      Sign up

      Join the Tech Talk
      Success! Thank you! Please check your email for further details.

      Please complete your information!

      Congratulations on unlocking the whale ambience easter egg!

      Click the whale button in the bottom left of your screen to toggle some ambient whale noises while you read.

      Thank you to the Glacier Bay National Park & Preserve and Merrick079 for the sounds behind this easter egg.

      Interested in whales, protecting them, and their connection to helping prevent climate change? We recommend checking out the Whale and Dolphin Conservation.

      Become a contributor for community

      Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

      DigitalOcean Documentation

      Full documentation for every DigitalOcean product.

      Resources for startups and SMBs

      The Wave has everything you need to know about building a business, from raising funding to marketing your product.

      Get our newsletter

      Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

      New accounts only. By submitting your email you agree to our Privacy Policy

      The developer cloud

      Scale up as you grow — whether you're running one virtual machine or ten thousand.

      Get started for free

      Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

      *This promotional offer applies to new accounts only.