DEV Community

Chimobi Roland
Chimobi Roland

Posted on

I Fixed My Slow Nginx + Gunicorn Setup — Here’s How It Became 3X Faster

Before we dive in, a quick disclaimer — I’m not a professional DevOps engineer, just a developer who needed to fix a slow server. After spending hours tweaking, I want to share my experience so you don’t waste hours on something that should take five minutes.

Now, here’s my setup: I’m running a Django app on a CentOS 9 EC2 Server. For my web server, I chose Nginx for three key reasons: performance, load balancing, and ease of configuration, along with my familiarity with it. I also used Gunicorn as the proxy server to manage the Django services.

Image description

The problem:

While testing, I noticed something odd — a 3x speed difference between running the Django development server with Nginx routing traffic to it versus running it through Gunicorn.

My first thought was that the application might be too heavy, so I enabled preload in Gunicorn. This preloads the app before forking worker processes, which can improve memory efficiency but doesn’t necessarily boost request speed. I also increased the number of workers from 2 to 4 to allow more concurrent requests and set the log level to error to reduce logging overhead.

Despite these tweaks, the requests were still 3x slower, suggesting the bottleneck might be elsewhere — possibly due to Gunicorn’s default synchronous workers, Nginx’s configuration, or database/I/O delays.

Image description

Below is my Nginx and Gunicorn configuration. By default, the Nginx config is located in:

/etc/nginx/conf.d/filename.conf

server {
    listen 80;
    server_name **.**.***.***;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/ec2-user/src/g*****/media;
    }

    location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Image description

I created a service file to run Gunicorn as a detached background service, ensuring it starts automatically when the server restarts and can handle incoming requests reliably.

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

[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory=/home/ec2-user/src/g*****
ExecStart=/home/ec2-user/src/g*****/vone/bin/gunicorn \
 --workers 4 \
 --timeout 60 \
 --bind 0.0.0.0:8000 \
 --access-logfile /home/ec2-user/src/g*****/access.log \
 --error-logfile /home/ec2-user/src/g*****/error.log \
 v1_0.wsgi:application
[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

After hours of debugging and tracing, I ruled out Nginx as the direct cause of the issue. The real culprit turned out to be a misconfigured binding address in Gunicorn.

I had set:

--bind 0.0.0.0:8000
Enter fullscreen mode Exit fullscreen mode

in my Gunicorn config and a similar address in Nginx:

proxy_pass http://localhost:8000;
Enter fullscreen mode Exit fullscreen mode

This single misconfiguration drastically slowed down my service. After a deep dive, I learned that this setup forced the server to communicate over the network stack instead of using a more efficient Unix socket or loopback interface :-/

The Solution:

To fix the issue, I followed three simple steps:

1. Create a Gunicorn Socket Service
First, I created a Gunicorn socket file to allow Gunicorn to

communicate via a Unix socket:
Enter fullscreen mode Exit fullscreen mode
sudo nano /etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
Enter fullscreen mode Exit fullscreen mode

2. Update Gunicorn to Use the Socket
Next, I modified the Gunicorn service file to bind to the new socket instead of a network address:

— bind unix:/run/gunicorn.sock

[Unit]
Description=gunicorn daemon
After=network.target
Enter fullscreen mode Exit fullscreen mode
[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory=/home/ec2-user/src/g*****
ExecStart=/home/ec2-user/src/g*****/vone/bin/gunicorn \
 --workers 3 \
 --timeout 60 \
 --bind unix:/run/gunicorn.sock \
 --access-logfile /home/ec2-user/src/g*****/access.log \
 --error-logfile /home/ec2-user/src/g*****/error.log \
 v1_0.wsgi:application
[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

3. Update Nginx to Use the Socket
Lastly, I then updated Nginx’s proxy_pass setting to communicate via the Unix socket:
proxy_pass http://unix:/run/gunicorn.sock;

server {
    listen 80;
    server_name **.**.***.***;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/ec2-user/src/g*****/media;
    }

    location / {
        proxy_pass http://unix:/run/gunicorn.sock;
        proxy_set_header Host $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;
    }
}
Enter fullscreen mode Exit fullscreen mode

After restarting the services using the below commands, the issue was eliminated by removing unnecessary network overhead, making the application much faster.

Sudo Systemctl restart Nginx
Sudo Systemctl restart Gunicorn
Enter fullscreen mode Exit fullscreen mode

I ran the test again on Postman, and voila — the response time for Gunicorn was ~3X faster.

Image description

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.