Skip to content

Instantly share code, notes, and snippets.

@janoliver
Last active January 17, 2024 12:44
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save janoliver/127091de8185cd8557ad to your computer and use it in GitHub Desktop.
Save janoliver/127091de8185cd8557ad to your computer and use it in GitHub Desktop.
Arch Linux as a Web Server

In this tutorial, I briefly explain how to set up a webserver using nginx, openssl and uwsgi on Arch Linux. The tutorial is applicable to other Linux distributions and goes through the required configuration step by step. It is, I believe, beginner friendly.

0. Version

I updated, changed and tested the tutorial on Dec. 8th, 2013. All commands and configuration files relate to the software versions that are currently available in the Arch package repositories. Especially nginx and uwsgi change often and quickly, so check out new cool features from time to time and follow there releases.

1. Introduction

Arch Linux is an amazing Linux distribution. Among other great things, it is extremely lightweight, ships with pacman, the best package manager I have seen so far, and has a great wiki with all you need to know to get it up and running.

After having used Arch on my laptop and desktop machine at work for a couple of years now, I decided to give it a try running on my webserver. Because it has worked reasonably stable for almost a year now and there are only very few tutorials for running Arch as a server, I decided to write a comprehensive tutorial for setting up a full web-server stack on Arch Linux.

Although written for Arch, this tutorial is in general applicable to any Linux distribution. Since Arch uses mostly standard locations for configuration files, the only Arch specific commands will be the installation of packages.

1.1 Attention!

One important thing must be said first. Arch is a rolling release distribution. Its repositories always contain the newest upstream versions of software and the linux kernel. This is usually not needed for server setups and there are a lot more stable and well-tested Linux distributions, like for example Debian Linux. Things may break! The more often you change or update software, especially when it is not thoroughly tested, the higher is the risk of rendering something unuseful, introducing security vulnurabilities or even causing boot- or runtime errors. For this reason, do only use Arch as a production server, when you are sure what you are doing, are able to fix things in some kind of rescue system and are not hosting extremely important web services or -sites.

In the (almost) ten months that I have been running Arch as a server, I experienced only a few critical problems and all of them could be fixed quickly. However, I did have more downtime than for instance Debian would have shown. So act on you own risk!

1.2 Non-Arch distributions

The tutorial is more or less easily applicable to other Linux distributions. You do need to know how to use the corresponding package manager to install software packages. Also, some newer software may not be in your repositories in which case you need to compile it yourself. Currently, this applies for instance to uwsgi on Debian.

Arch switched to the systemd system daemon, a rather new init system. Therefore the startup of daemons or services may be different for your distribution. For example, starting the reverse proxy nginx requires executing

# systemctl start nginx

on Arch, but you would need to run

# /etc/init.d/nginx start

on Debian based distributions.

1.3 The webserver stack

This tutorial is about webservers. By webserver I mean the following stack of applications:

  • nginx: A webserver to serve static files and a reverse proxy for our application-specific servers. I will include the inclusion of ngx_pagespeed, the pagespeed module by google.
  • uwsgi: A stack of servers (or servlets) for our web applications. I will show how to set up the uwsgi emperor to make deployment of additional apps as easy as possible.
  • OpenSSL: I will spend some time explaining how to create SSL certificates for secure browsing.
  • the application: Be it written in PHP, Python, Lua, etc., this is the actual program we want to serve.

All parts of this setup can be replaced by other software. Feel free to skip chapters to read only the part on a single application. They are mostly decoupled anyway.

1.4 Prerequisites

You should have a ready to use Arch Linux system set up. It does not need a graphical user interface. You need root access and a working network connection. Before you start, update your system once again by executing

# pacman -Syu

We will use the user http with its home directory /srv/http for the webserver. For some reason, that directory is not owned by the user http on a fresh Arch install, so that we have to adjust permissions:

# chown http:http /srv/http

Also, no default shell is set for http, so set one. (Replace zsh with whichever shell you like most)

# chsh -s /usr/bin/zsh http

1.5 Terminology

Before we begin, some notes on how to read the tutorial:

  • I will most of the times say Arch instead of Arch Linux and you may replace that with the name of your preferred Linux distribution in your head.
  • CLI commands are prefixed with a #, when executed as root and with $ when executed as some other user. The user is, in all cases, http! Messing that up may break file permissions!
  • Typos are intended!

1.6 The project structure

Especially when hosting multiple sites that are served by multiple servlets, it is helpful to have a smart and consistend file and folder structure for the different projects. Both uwsgi and nginx, the servers we are using, support globbing in their configuration files. That means we can have them look for config files in, say, /path/to/projects/*/*.conf, where * is the name of different projects.

Arch ships with a folder /srv/http, which is the home directory of the http user. This is the perfect place for our projects to reside. I suggest and use the following structure for my projects:

  • /srv/http/project1
    • nginx.conf: The nginx configuration file
    • uwsgi.ini: The uwsgi configuration file
    • log/: All the logs for this project are stored here
    • run/: pid files and sockets are created here
    • htdocs/: This is where the application files reside.
  • /srv/http/project2 ...

This setup is portable and keeps everything belonging to a single project in the corresponding folder. It also separates application logic (htdocs/) from run files and so on.

2. Creating our example project

Throughout the tutorial, we will work with an example project. Let us first create its files and folders which we will edit later. Following the project structure explained above, these commands will create the necessary structure. Please note, that in future I will skip the su (set user) to http. For commands prefixed with a $ sign, I assume the current user to be http.

# su http
$ cd
$ mkdir -p example/log example/run example/htdocs
$ touch example/nginx.conf example/uwsgi.ini
$ touch example/htdocs/index.html example/htdocs/index.py

These are all files we'll need.

3. Setting up nginx as a static files server

Let us begin by installing and setting up nginx to serve static files. Later, we will also configure it as a reverse proxy to serve our dynamic web applications.

3.1 What is nginx?

nginx (pronounced "engine x") is a webserver and reverse proxy for the HTTP protocol. It can also serve email stuff, but we will focus on serving websites. nginx is a bit more than ten years old and was written by a russian guy named Igor Sysoev. Recently, nginx was turned into a company and received a good amount of funding. It is among the three most popular webservers, together with Apache and Microsoft-IIS.

Other than its big and famous brother Apache, it only serves static files, like html, css and javascript and can act as a reverse proxy. It is NOT capable of handling dynamic stuff itself, i.e. spawning PHP interpreters or managing fCGI servers. Instead, it routes traffic to such servers through sockets (or via local network) and serves their responses to the client. This is the reverse proxy part of nginx.

I use nginx, because it has a very low memory footprint, is extremely fast and very actively developed. Considering the recent funding, at least within the next few years it probably will only become better and more famous, so it is a somewhat future-proof choice.

3.2 Installing nginx

Install nginx by executing

# pacman -S nginx

Easy.

3.3 Configuring nginx

Let us clean up the default configuration file shipped with nginx and change some stuff. My nginx configuration looks like this at the moment:

user http;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include mime.types;
    default_type application/octet-stream;

    sendfile on;
    keepalive_timeout 65;
    gzip on;

    include /srv/http/*/nginx.conf;
}

The differences to the default configuration are the following:

  • I removed all commented lines.
  • I set user http; because I want the webserver to run under this uid
  • I uncommented gzip on;
  • I added include /srv/http/*/nginx.conf; to the bottom of the http block.

nginx will now look in /srv/http/*/nginx.conf for additional configuration files. Each time you add an application in /srv/http with an nginx.conf file and reload the webserver, this will be read and applied. This implies, that a mistake in any of the files will prevent the server from running at all, so be careful!

3.4 Configuring the project

Let us now try to serve a Hello World with nginx. Put some string into /srv/http/example/htdocs/index.html:

$ cd ~/example
$ echo "Hello World\!" > htdocs/index.html

Now, we edit /srv/http/example/nginx.conf so that nginx knows about our project:

server {
    listen 80;
    server_name example.YOURDOMAIN;

    error_log /srv/http/example/log/nginx.error.log;
    access_log /srv/http/example/log/nginx.access.log;

    root /srv/http/example/htdocs;
    index index.html;
}

Don't forget to replace YOURDOMAIN with your domain, of which example. will be the subdomain to our project. The server_name directive should contain the domains under which your project should to be accessible. It may contain multiple domains separated by spaces, i.e.,

server_name www.example.com example.com subd1.example.com;

3.5 Restart nginx and visit the website

Now, simply reload the nginx config, pray that everything works fine and visit your projects address:

# systemctl start nginx

You should now be able to type the project domain (example.YOURDOMAIN above) in the browsers address field and be greated with

Hello World!

4. Serving dynamic pages with uWSGI

nginx is working now, so let's go on by setting up uwsgi to serve some dynamic pages like PHP, Python, Lua and so on.

4.1 What is uWSGI?

uWSGI is a protocol that can be used to deploy dynamic web applications with webservers like nginx. To use it, you need to have a uWSGI server, of which the most popular one is uwsgi, developed by the italian company unbit. It was originally created to serve Python applications, hence the WSGI in the name, but it now includes many more components, i.e., CGI, PHP, Lua, Rack, Go, etc.

uwsgi is highly configurable and follows a one-app-per-server approach. Each application will have its own uwsgi configuration file (and on or multiple uwsgi server threads), in which the configuration can be adjusted to match the purpose of the application as nicely as possible.

uwsgi is perfect when you want to serve all sorts of different applications. I am using it (among other stuff) to serve django (Python), cgit (CGI) and Drupal (PHP) sites. In our setup, we will make use of the uwsgi emperor. The emperor is a process that looks for uwsgi configuration files in certain directories, called vassals, and manages the corresponding uwsgi servers. It watches over the vassals and is capable of heal the servers after a crash.

4.2 Install uWSGI

I recommend installing uwsgi following a modular approach.

# pacman -S uwsgi-plugin-common

You might need to install some dependencies first. As an example, we will serve a python app, so we also need the Python plugin of uwsgi.

# pacman -S uwsgi-plugin-python2

The packages do not include any init scripts or systemd services. Therefore, create the file /etc/systemd/system/uwsgi.service with the following content:

[Unit]
Description=uWSGI Server

[Service]
ExecStart=/usr/bin/uwsgi --emperor "/srv/http/*/uwsgi.ini" --uid=http --gid=http --vassals-inherit /srv/http/base.uwsgi.ini
SuccessExitStatus=30
ExecReload=/bin/kill -HUP $MAINPID
KillSignal=SIGINT
Restart=always
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target

The unit spawns the uwsgi emperor who looks in /srv/http/*/uwsgi.ini for application-specific uwsgi configuration files. The processes are started as the http user and group. All configuration files will share the content in /srv/http/base.uwsgi.ini. This is to save typing in additional config files.

4.3 Configure your project for use with uwsgi

First, set up the base uwsgi config file /srv/http/base.uwsgi.ini mentioned above:

[uwsgi]
uid = http
gid = http

Though these two lonely directives seem funny, this would be the place to put config entries, that are shared by all the vassals, i.e., the actual applications in /srv/http. An option in a vassal's uwsgi.ini always overrides the value in the base.uwsgi.ini. Now, let us configure our project to be served via uwsgi. Edit the /srv/http/example/uwsgi.ini file and insert the following:

[uwsgi]
plugin = python2
socket = %drun/%n.sock
chdir = %dhtdocs
wsgi-file = index.py
master = True
pidfile = %drun/%n.pid
logto = %dlog/%n.log

The variables %d and %n contain the project path (with trailing slash, unfortunately) and the filename of the uwsgi config file without the extension, respectively. In the config, we specify the plugin to use (python2), the socket and pidfile location, the location of the logfile and the user and group for this vassal.

With chdir= we change into our projects htdocs/ directory and wsgi-file is the python file that contains the application callable (see next section).

4.4 Configure nginx so it can talk to your uwsgi server

In the section above, we defined a socket location for uwsgi. We will now configure nginx to use that socket when it wants to talk to uwsgi. Edit /srv/http/example/example.nginx.conf and change it to the following snippet:

server {
    listen 80;
    server_name example.YOURDOMAIN;

    error_log /srv/http/example/log/error.log;
    access_log /srv/http/example/log/access.log;

    location / {
            include uwsgi_params;
            uwsgi_modifier1 30;
            uwsgi_pass unix:/srv/http/example/run/uwsgi.sock;
    }
}

uwsgi_modifier1 defines the type of uWSGI request. You can read about its values here. With uwsgi_pass we tell nginx where to look for the socket. The inclusion of uwsgi_params sets some default values for the variables.

We can drop the root and index directives from the nginx.conf file, because the behaviour and paths are configured in our uwsgi.ini file and taken care of by uwsgi.

4.5 Create a hello world python application

The last step for our running python app is the application itself. We will simply use the example from Wikipedia. Edit /srv/http/example/htdocs/index.py and insert the following lines of python code:

def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return "Hello World from python!\n"

This is a minimal example for a WSGI app. It simply defines a callable (application()) that initializes a HTTP response and returns some content. Of course, you will want to use some web framework for more sophisticated applications. Great examples are flask, webpy, Django etc.

4.6 Start/restart the servers and try the app

If you made no mistakes during the configuration, you should now be able to restart the servers and visit your app.

# systemctl start uwsgi
# systemctl reload nginx

You can inspect the uwsgi emperor process using systemctl:

# systemctl status uwsgi
    uwsgi.service - uWSGI Server
   Loaded: loaded (/etc/systemd/system/uwsgi.service; disabled)
   Active: active (running) since Sun 2013-12-08 19:39:23 UTC; 2min 45s ago
 Main PID: 529 (uwsgi)
   Status: "The Emperor is governing 1 vassals"
   CGroup: /system.slice/uwsgi.service
           ├─529 /usr/bin/uwsgi --emperor /srv/http/*/uwsgi.ini --uid=http --gid=http --vassals-inherit /srv/http/base.uwsgi.ini
           ├─546 /usr/bin/uwsgi --ini /srv/http/example/uwsgi.ini --inherit /srv/http/base.uwsgi.ini
           └─552 /usr/bin/uwsgi --ini /srv/http/example/uwsgi.ini --inherit /srv/http/base.uwsgi.ini

Looks good, hm? Now, by visiting http://example.YOURDOMAIN with your browser, you should see the python generated Hello World from python! greeting. Success!

5. Securing your websites with OpenSSL

In this section, I will briefly explain how to enable SSL support for your nginx projects. We will create a self-signed SSL certificate and configure nginx to use it for your sites.

5.1 About SSL

It is not that easy to explain SSL (Secure Sockets Layer) in just a few sentences. Basically the communication between the client and the server is encrypted. The authentication is determined once per request by handshaking during which the servers certificate must be accepted by the client. This happens automatically, when the client already knows about the CA (certificate authority) that signed the servers certificate. Several root certificates of such CAs are built into OSes, Browsers and so on, but it is usually quite expensive to have your certificate signed by those CAs. We will therefore sign our certificates ourselves. The downside of that is that each client must accept our certificate manually before he can talk to our server.

You can read more about SSL on Wikipedia.

5.2 Create your own SSL certificates

In case you don't have OpenSSL installed (it's a dependency of OpenSSH, so you probably have it already), do it now:

# pacman -S openssl

The following steps are shamelessly stolen from here. Sorry for that!

First, let us create some temporary folder to work in:

$ cd $(mktemp -d)

Now, we create our private key. A passphrase must be given, but we can remove it later.

$ openssl genrsa -des3 -out myssl.key 1024

For our key, we create a certificate signing request. In commercially signed certificates, this is what we would send to the CA. You will be asked some information about your certificate. For the common name insert the domain you want to secure.

$ openssl req -new -key myssl.key -out myssl.csr

This is the example output of my CSR:

Enter pass phrase for myssl.key: *******
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:Hessen 
Locality Name (eg, city) []:Marburg
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.YOURDOMAIN           
Email Address []:YOU@YOURDOMAIN

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Now we remove the passphrase from our private key:

$ cp myssl.key myssl.key.org
$ openssl rsa -in myssl.key.org -out myssl.key

And finally, we can create our SSL certificate:

$ openssl x509 -req -days 365 -in myssl.csr -signkey myssl.key -out myssl.crt

Done! In your temporary folder there should be a myssl.key and myssl.crt file. Those two files are all we need. Let us move them to some more appropriate location.

# cp myssl.crt /etc/ssl/certs/example.YOURDOMAIN.crt
# cp myssl.key /etc/ssl/private/example.YOURDOMAIN.key

5.3 Configure nginx for SSL encryption.

Now, we need to configure nginx for SSL encryption. Change your /srv/http/example/example.nginx.conf to the following snippet:

server {
    listen 443 ssl;
    server_name example.YOURDOMAIN;

    error_log /srv/http/example/log/error.log;
    access_log /srv/http/example/log/access.log;

    ssl_certificate     /etc/ssl/certs/example.YOURDOMAIN.crt;
    ssl_certificate_key /etc/ssl/private/example.YOURDOMAIN.key;

    location / {
            include uwsgi_params;
            uwsgi_modifier1 30;
            uwsgi_pass unix:/srv/http/example/run/uwsgi.sock;
    }
}

That's it! Restart nginx...

# systemctl reload nginx

... and visit https://example.YOURDOMAIN. If you have to accept some certificate, you succeeded. Congratulations!

6. Log rotation

As Christian thankfully pointed out in the comments, we want our log files to be rotated so they don't grow too large. This is as simple as adding the file /etc/logrotate.d/webserver with the following content:

/srv/http/*/log/*.log {
    compress
    rotate 4
    size 1024k
    notifempty
    missingok
    noolddir
}

Now, once a day the tool logrotate checks all our logfiles and, if they grew larger than 1024k, rotates them.

7. About databases

What is usually part of dynamic web apps, is a database. Recently, SQLite received much attention, but of course there are others. Since databases are so decoupled from the contents of this tutorial, I will not go through the steps of setting one up.

I myself have an instance of MariaDB (replacement of MySQL) running on my server. I usually create for each project a single database and its own database user. This enhances portability. Another popular and probably better choice than MariaDB is PostgreSQL.

Literature

The last part of the tutorial is some further reading suggestions. The following links a worth reading and will help you setting up additional stuff in web- servers.

The end.

I really hope that this tutorial is helpful for somebody. I enjoy working with nginx and uwsgi and hope to spread this joy. Also, Arch rules! I am glad to answer questions in the comments!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment