How does my Django site connect to the internet anyway?

I created a Django site to troll my cousin Barry who is a big San Diego Padres fan. Their Shortstop is a guy called Fernando Tatis Jr. and he’s really good. Like really good. He’s also young, and arrogant, and is everything an old dude like me doesn’t like about the ‘new generation’ of ball players that are changing the way the game is played.

In all honesty though, it’s fun to watch him play (anyone but the Dodgers).

The thing about him though, is that while he’s really good at the plate, he’s less good at playing defense. He currently leads the league in errors. Not just for all shortstops, but for ALL players!

Anyway, back to the point. I made this Django site call Does Tatis Jr Have an Error Today?It is a simple site that only does one thing ... tells you if Tatis Jr has made an error today. If he hasn’t, then it says No, and if he has, then it says Yes.

It’s a dumb site that doesn’t do anything else. At all.

But, what it did do was lead me down a path to answer the question, “How does my site connect to the internet anyway?”

Seems like a simple enough question to answer, and it is, but it wasn’t really what I thought when I started.

How it works

I use a MacBook Pro to work on the code. I then deploy it to a Digital Ocean server using GitHub Actions. But they say, a picture is worth a thousand words, so here's a chart of the workflow:


This shows the development cycle, but that doesn’t answer the question, how does the site connect to the internet!

How is it that when I go to the site, I see anything? I thought I understood it, and when I tried to actually draw it out, turns out I didn't!

After a bit of Googling, I found this and it helped me to create this:

Internal workings

My site runs on an Ubuntu 18.04 server using Nginx as proxy server. Nginx determines if the request is for a static asset (a css file for example) or dynamic one (something served up by the Django App, like answering if Tatis Jr. has an error today).

If the request is static, then Nginx just gets the static data and server it. If it’s dynamic data it hands off the request to Gunicorn which then interacts with the Django App.

So, what actually handles the HTTP request? From the answer above:

[T]he simple answer is Gunicorn. The complete answer is both Nginx and Gunicorn handle the request. Basically, Nginx will receive the request and if it's a dynamic request (generally based on URL patterns) then it will give that request to Gunicorn, which will process it, and then return a response to Nginx which then forwards the response back to the original client.

In my head, I thought that Nginx was ONLY there to handle the static requests (and it is) but I wasn’t clean on how dynamic requests were handled ... but drawing this out really made me stop and ask, “Wait, how DOES that actually work?”

Now I know, and hopefully you do to!


These diagrams are generated using the amazing library Diagrams. The code used to generate them is here.

Whoops! Or how I broke my website by installing Nginx with Apache

I’ve been working on a project to create a Django based website. Over the weekend (Saturday I think) I tried to get it up and running on my Linode server. However, after a couple of failed attempts I decided to use the free hosting coupon1 I had for DigitalOcean to see if that allowed me to reply more easily deploy … the short answer … meh.

What I didn’t realize over the weekend is that while I had been trying to deploy my Django site, I had installed Nginx on my Linode server that was also running apache2. This lead to them both trying to listen on port 80 but because Nginx was the last thing I had kicked off, it was winning.

While I was working on my Django site I should have realized that something was up when I tried to connect to the blog for the site (still a Wordpress site on my Linode server) and it returned a ‘Can not connect to the server message’. I didn’t pay much attention because I figured (incorrectly) that I had done something specific to that subdomain, and not that I had made all of the sites on my Linode server inaccessible.

Last night at about 9 I thought, “Well, it should’t take long for me to figure out the issue with the new blog. ”

By 10:15 I tried everything the internet had told me to try and I was still unable to get apache2 to reload.

I googled a bunch of stuff, but nothing was helping.

When I tried to get the status on apache2 I would get this:

 apache2.service - LSB: Apache2 web server
   Loaded: loaded (/etc/init.d/apache2; bad; vendor preset: enabled)
  Drop-In: /lib/systemd/system/apache2.service.d
   Active: inactive (dead) since Tue 2018-05-01 05:01:03 PDT; 5s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 7718 ExecStop=/etc/init.d/apache2 stop (code=exited, status=0/SUCCESS)
  Process: 7703 ExecStart=/etc/init.d/apache2 start (code=exited, status=0/SUCCESS)

May 01 05:01:03 milo apache2[7703]: (98)Address already in use: AH00072: make_sock: could not bind to address [::]:80
May 01 05:01:03 milo apache2[7703]: (98)Address already in use: AH00072: make_sock: could not bind to address
May 01 05:01:03 milo apache2[7703]: no listening sockets available, shutting down
May 01 05:01:03 milo apache2[7703]: AH00015: Unable to open logs
May 01 05:01:03 milo apache2[7703]: Action 'start' failed.
May 01 05:01:03 milo apache2[7703]: The Apache error log may have more information.
May 01 05:01:03 milo apache2[7703]:  *
May 01 05:01:03 milo apache2[7718]:  * Stopping Apache httpd web server apache2
May 01 05:01:03 milo apache2[7718]:  *
May 01 05:01:03 milo systemd[1]: Started LSB: Apache2 web server.

This morning I started to google each line of the status message and finally got to this:

no listening sockets available, shutting down

Googling for that lead me to trying this:

sudo netstat -ltnp | grep ':80'

Which output this:

tcp        0      0    *               LISTEN      3324/nginx -g daemo
tcp6       0      0 :::80                   :::*                    LISTEN      3324/nginx -g daemo

And finally, I saw the issue. Over the weekend while I was futzing around I had apparently installed Nginx and let it listen on port 80 AND kept it running.

Once I killed the Nginx process with this:

sudo kill -9 3324

I was able to restart apache2 with no problems.

Thank goodness.

I find that when I mess something up like this it’s important to ask myself what I learned from the experience.

In that vein …

What did I learn from this experience?

  1. Can’t run apache2 and Nginx on the same server and have them listen on the same port. Seems obvious, but you know having to actually deal with it really seals the deal
  2. The output messages are super helpful … google each part of them and don’t give up
  3. A good night’s sleep can make all the difference
  4. Rolling your own web server is less expensive than having it be Turnkey (a la SquareSpace, or some other hosted solution) but you end up being your own Sys Admin and that’s actually pretty easy when things are going well, and a freaking nightmare when they’re not
  1. [Thanks to the Talk Python to Me Course for Entrepreneurs