Ultimate NGINX reverse proxy to WordPress / PHP / Python / Django / Golang / Flask

For years, I’ve relied on Apache web server on my personal machine to continually hone my web programming chops and it doesn’t hurt to readily have a working server configuration for quick reference.

While Apache has performed brilliantly, it was time to switch and get more comfortable with NGINX. It is frequently chosen to reverse proxy and route HTTP traffic towards remote processes which actually ingest the web requests and process them.

In the real world, with reverse proxying, NGINX can manage many thousands of requests by offloading the dirty work to other servers to handle.  In my small development setup with all the required processes running on the same machine, NGINX merely hands off the work onto these local services and it all stays on the same machine.

I like to have several resources at my disposal to muck around with.  These include:

  1. WordPress blog site which is hosted at the default location ‘/’
  2. phpMyAdmin which greatly simplifies MySQL database administration stuff.
  3. Playground for my PHP programming stuff
  4. Playground for my Python progamming stuff
  5. A Django web framework site that I can beat up on
  6. Playground for my Go programming stuff.
  7. Playground for Flask web framework

My desired configuration is pictured below:

nginx

The below lists all the steps to get a fully working NGINX reverse proxy to all these resources, located at their respective branch off the default web domain.

Most tutorials out there will tell you how to set up each of these resources but assumes that you want it to be seen at the default ‘/’ location which doesn’t work well when you want multiple stuff running at the same time.  It makes more sense to split them off into their own branch off the default web location.

This was done on a vanilla Ubuntu system.  I used the localhost server since this was limited to my machine for development reasons.  Adjust as needed for a publicly accessible server with a fully qualified domain name.


Reverse proxy to phpmyadmin and default WordPress site

The goal here:

http://site.com/ → to wordpress
http://site.com/phpmyadmin → to phpmyadmin

sudo apt-get install nginx
sudo service nginx start

sudo apt-get install phpmyadmin     (ignore when asked for apache2 or lighttpd)

phpmyadmin installed in /usr/share/phpmyadmin

sudo apt-get install php5-fpm
sudo vi /etc/php5/fpm/php.ini and edit:

cgi.fix_pathinfo=0

sudo vi /etc/php5/fpm/pool.d/www.conf and edit:

listen = /var/run/php5-fpm.sock

sudo service php5-fpm restart
sudo vi /etc/nginx/sites-available/default
(This is not the final configuration but will get phpmyadmin working)

note that: /phpMyAdmin will be redirected to /phpmyadmin

server {
   listen 80;
   root /usr/share/nginx/html;
   index index.php index.html index.htm;

   server_name localhost;

   location / {
      try_files $uri $uri/ /index.html;
   }

   error_page 404 /404.html;

   error_page 500 502 503 504 /50x.html;
   location = /50x.html {
      root /usr/share/nginx/html;
   }

# pass the PHP scripts to FastCGI server listening on the php-fpm socket
   location ~ \.php$ {
      try_files $uri =404;
      fastcgi_pass unix:/var/run/php5-fpm.sock;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
   }

   location /phpmyadmin {
      root /usr/share/;
      index index.php index.html index.htm;
      location ~ ^/phpmyadmin/(.+\.php)$ {
         try_files $uri =404;
         root /usr/share/;
         fastcgi_pass unix:/var/run/php5-fpm.sock;
         fastcgi_index index.php;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
         include fastcgi_params;
      }
      location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ {
           root /usr/share/;
       }
   }
   location /phpMyAdmin {
      rewrite ^/* /phpmyadmin last;
   }
}

service nginx reload


Reverse proxy to Default WordPress site

WordPress was already installed in /var/www/www.jaredlog.com so the steps for installation of WordPress won’t be covered here.  Plenty of online tutorials out there to help you with this.

edit /etc/nginx/sites-available/default

root /var/www/www.jaredlog.com;
index index.php index.html index.htm;
server_name localhost;

location / {
   try_files $uri $uri/ /index.html;
}

error_page 404 /404.html;

error_page 500 502 503 504 /50x.html;
location = /50x.html {
   root /usr/share/nginx/html;
}

# pass the PHP scripts to FastCGI server listening on the php-fpm socket
location ~ \.php$ {
   try_files $uri =404;
   fastcgi_pass unix:/var/run/php5-fpm.sock;
   fastcgi_index index.php;
   fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
   include fastcgi_params;
}

Reverse proxy to uWSGI python with unix socket

The goal here:

http://site.com/pythontest → to Python playground

With python stuff, we will make use of virtual environments to simplify the versioning of python and included packages.

sudo apt-get install python-dev python-pip

sudo pip install virtualenv

cd /var/www/pythontest
virtualenv pytontestenv

Activate the virtual environment
source pythontestenv/bin/activate

(deactivate later on after installation of packages).

pip install uwsgi
uwsgi –version

Setup a simple uWSGI app with unix socket for less overhead
(the Django w/Guincorn setup is mentioned later on)

edit /var/www/pythontest/wsgi.py

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ["<h1 style='color:blue'>Hello There, this is WSGI!</h1>"]

Test it
uwsgi –socket 0.0.0.0:8080 –protocol=http -w wsgi

Open web browser and visit http://localhost:8080

Can deactivate virtual environment now
deactivate

edit /var/www/pythontest/pythontest.ini

[uwsgi]
module = wsgi:application

master = true
processes = 5

socket = pythontest.sock
chmod-socket = 664
vacuum = true

die-on-term = true

edit /etc/init/pythontest.conf

description "uWSGI instance to serve pythontest"

start on runlevel [2345]
stop on runlevel [!2345]

setuid www-data
setgid www-data

script
   cd /var/www/pythontest
   . pythontestenv/bin/activate
   uwsgi -ini pythontest.ini
end script

sudo stop pythontest ; sleep 2 ; sudo start pythontest

See it running:
ps wwwauxg | grep uwsgi

add to /etc/nginx/sites-available/default

location /pythontest {
   include uwsgi_params;
   uwsgi_pass unix:/var/www/pythontest/pythontest.sock;
}

sudo service nginx configtest
sudo stop pythontest ; sleep 2 ; sudo start pythontest
sudo service nginx restart


Django installation with Gunicorn, MySQL and unix socket

The goal here:

http://site.com/djangotest → to Django playground

Create MySQL database djangotest

sudo apt-get install libmysqlclient-dev

cd /var/www/djangotest
source djangotestenv/bin/activate
pip install MySQL-python
pip install django
pip install gunicorn
deactivate

django-admin.py startproject djangotest .

edit /var/www/pythontest/djangotest/djangotest/settings.py

DATABASES = {
   'default': {
      'ENGINE': 'django.db.backends.mysql',
      'OPTIONS': {
          'read_default_file': '/var/www/pythontest/djangotest/my.cnf',
      },
   }
}

add to end of settings.py

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

Create /var/www/pythontest/djangotest/my.cnf

[client]
database = djangotest
host = localhost
user = dbuser
password = yayouwish
default-character-set = utf8

./manage.py makemigrations
./manage.py migrate
./manage.py createsuperuser
./manage.py collectstatic

edit urls.py:

urlpatterns = [
   url(r’^djangotest/admin/’, include(admin.site.urls)),
   url(r’^djangotest/’, include(admin.site.urls)),
]

./manage.py runserver 0.0.0.0:8000

Open web browser to localhost:8000 to confirm.

localhost:8000/admin (log into django)

Gunicorn testing

source djangotestenv/bin/activate
gunicorn –bind 0.0.0.0:8000 djangotest.wsgi:application

confirm via web browser on port 8000
deactivate

/etc/init/djangotest.conf

description "Gunicorn instance to serve djangotest"

start on runlevel [2345]
stop on runlevel [!2345]

setuid www-data
setgid www-data

script
   cd /var/www/djangotest
   . djangotestenv/bin/activate
   /var/www/djangotest/djangotestenv/bin/gunicorn –workers 3 –bind unix:/var/www/djangotest/djangotest.sock djangotest.wsgi:application
end script

sudo stop djangotest ; sleep 2 ; sudo start djangotest

nginx configuration for django:

location /static/ {
   root /var/www/djangotest;
}

location /djangotest {
   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:/var/www/djangotest/djangotest.sock;
}

Reverse proxy to Go

The goal here:

http://site.com/gotest → to Golang playground

GVM makes it easier to develop with different versions of Go and packages your projects need.   Visit:  https://github.com/moovweb/gvm  for more information

sudo apt-get install curl git mercurial make binutils bison gcc

bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)

gvm listall

gvm install go1.4.2
gvm use go1.4.2 –default
gvm list

source ~/.gvm/scripts/gvm

cd ~
mkdir -p go/{bin,pkg,src}

Put in .bashrc or .bash_profile

export GOPATH=$HOME/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOPATH/bin

cd ~/go/src
mkdir hello
cd hello

vi hello.go

package main

import (
   "fmt"
   "net/http"
)

func main() {
   http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "Hello there, Gopher")
   })

   http.ListenAndServe(":"3000″, nil)
}

go run hello.go

Open web browser to localhost:3000 and confirm you see the output.

sudo vi /etc/nginx/sites-enabled/default and add:

location /gotest {
   proxy_set_header X-Real-IP $remote_addr;
   proxy_set_header X-Forwarded-For $remote_addr;
   proxy_set_header Host $host;
   proxy_pass http://127.0.0.1:3000;
}

Open web browser to localhost/gotest and you should see the same output

go install hello.go     (installs hello binary in your ~/go/bin directory)

cp ~/go/bin/hello   /var/www/gotest

edit /etc/init/gotest.conf

description "instance to serve gotest"

start on runlevel [2345]
stop on runlevel [!2345]

setuid www-data
setgid www-data

script
   exec /var/www/gotest/hello
end script

stop gotest ; sleep 2 ; start gotest


Reverse proxy to Flask with unix socket

The goal here:

http://site.com/flasktest → to Flask playground

mkdir /var/www/flasktest
cd /var/www/flasktest/
virtualenv flasktestenv

. flasktestenv/bin/activate
pip install Flask
pip install uwsgi
deactivate

vi flasktest.py

from flask import Flask
app = Flask(__name__)
app.debug = True

@app.route('/flasktest')
def index():
   return 'Flask Index Page'

@app.route('/flasktest/hello')
def hello():
   return 'Hello World from Flask'

edit /var/www/flasktest/wsgi.py

from flasktest import app

if __name__ == "__main__":
   app.run()

. flasktestenv/bin/activate
uwsgi –socket 0.0.0.0:8000 –protocol=http -w wsgi

verify working by checking via web browser at http://localhost:8000/flasktest
deactivate

/var/www/flasktest/flasktest.ini

[uwsgi]
module = wsgi

master = true
processes = 5

socket = flasktest.sock
chmod-socket = 660
vacuum = true

die-on-term = true

/etc/init/flasktest.conf

description "uWSGI instance to serve flasktest"

start on runlevel [2345]
stop on runlevel [!2345]

setuid www-data
setgid www-data

script
   cd /var/www/flasktest
   . flasktestenv/bin/activate
#   uwsgi --ini flasktest.ini
    uwsgi -s flasktest.sock --catch-exceptions --module flasktest --callable app
#   uwsgi --catch-exceptions --ini flasktest.ini
end script

/etc/nginx/sites-available/default

   location /flasktest {
        include uwsgi_params;
        uwsgi_pass unix:/var/www/flasktest/flasktest.sock;
   }

stop flasktest ; sleep 2 ; start flasktest

service nginx restart

open web browser to http://localhost/flasktest


Finally, here’s the final NGINX configuration in all its glory:

default.nginx.config

If you have better ideas for the NGINX configuration than what is seen here, feel free to comment below!


 

This entry was posted in Linux, Programming. Bookmark the permalink.
  • Mirjam

    Thank you so so much!