Configurar NGINX para usar páginas de errores personalizadas.

En este breve tutorial voy a explicar como resolver una necesidad bastante común cuando se está configurando un servidor web:  crear páginas personalizadas para los errores, tanto para los 400s como para los 500s.

Sé que es algo de lo más común y solía hacerlo cuando usaba apache pero ahora que necesité hacerlo en NGINX en un servidor con Linux, sí encontré algunos detalles que me gustaría documentar.

Empezaré por los detalles que me motivaron a hacer este tutorial; la creación de los archivos y la ruta dónde deben ir almacenados.

Primero, es crear un archivo que llamaremos 500.html, este lo estoy creando en mi home dentro de la aplicación:

/home/user/webapp/500.html

Es un archivo sencillo que solo mostrará el error con una imagen y el logo de la empresa.

Este archivo, en algunos sitios o foros se menciona que se almacene en la raíz de la aplicación web o del hosting o que se use la ruta absoluta en la configuración y precisamente esa ambigüedad fue la que hizo que me tomara más tiempo realizar la configuración.

En realidad, ese archivo debe residir en /usr/share/nginx/html/ para ser visto desde la configuración default de nuestro servidor, pero como quizá se estén haciendo cambios de esa página de error, lo ideal fue dejarla cerca de la raíz de mi webapp y solo crear un “link” para que la vea el nginx en la ruta esperada y eso lo hice de la siguiente manera:

ln /home/user/webapp/500.html /usr/share/nginx/html/500.html

Lo siguiente es modificar nuestro archivo de configuración:

/etc/nginx/sites-enabled/default

Y ahí, agremos lo siguiente:

error_page 502 /500.html;
location /500.html{
}

En mi configuración, tengo todo redireccionado a https, entonces las lineas anteriores van dentro de la sección de server como se observa a continuación:

 

Una vez modificado ese archivo, solo resta validar que esté bien la modificación y reiniciar el nginx y listo.

Para verificar que todo esté bien se utilizar sudo nginx -t y nos arroja lo siguiente:

user@lap:~# sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Y solo resta reiniciarlo:

sudo service nginx restart

Con eso, ya tenemos configurado nuestro servidor para mandar páginas de errores personalizadas.

Solo como comentario adicional, en la documentación  de nginx se permite redireccionar directamente a una URL pero esto en mi ambiente de desarrollo complicaba algunas cosas dado que la plataforma está con node.js y esto requería un poco más de configuración a nivel de desarrollo.

 

Pongámonos técnicos

Primero, un poco de contexto y algunos detalles.

He estado trabajando en una aplicación web y el flujo de desarrollo y deployment ha sido muy simple. Tenemos todo el hosting montado en un VPS de digitalocean y todo el código está en bitbucket.org (básicamente porque se pueden tener repositorios privados de manera gratuita, contrario a github.com) y en el mismo bitbucket llevamos todo el seguimiento de los issues, cambios y requerimientos.

En el VPS, tenemos el servidor de producción y cada vez que se realiza un push al bitbucket, entramos al servidor y con un pequeño script, actualizamos el código y al mismo tiempo reiniciamos la aplicación (corre en Node.JS):

#!/bin/sh
cd /webapp
git checkout .;
git pull;
ps -ef | grep node | grep webapp | awk '{print $2}' | xargs kill;
exit $?

Esto de tener que entrar al servidor y ejecutar el deployment manual (aunque no es lo más recomendado), nos ha dado la oportunidad de lanzar código a producción solo después de pasar por varias pruebas locales y además, que solo tengan accesos las personas que puedan trabajar con esa información sensible de usuarios reales.

Sin embargo, a estas alturas del desarrollo, es necesario trabajar con un stage porque vamos a realizar unos cambios considerables que van a romper algunas cosas de la plataforma y además, habrá más personajes involucrados en el desarrollo y necesitarán ver en “vivo” los cambios que se vayan haciendo.

Por esa razón y porque tener un stage es parte del proceso de desarrollo, vamos a hacer la configuración para que esto funcione.

En el ambiente local

Primero, vamos a tener que generar las llaves para conectarnos mediante SSH al servidor de producción:

ssh-keygen -C "usuario@dominio.com"

Esto genera como resultado lo siguiente y dos archivos, que uno de ellos lo usaremos más adelante (id_rsa.pub):

[ arathvelazquez@Air:~/.ssh ] $ ssh-keygen -C "usuario@dominio.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/arathvelazquez/.ssh/id_rsa): id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in test_rsa.
Your public key has been saved in test_rsa.pub.
The key fingerprint is:
98:2f:bd:ba:ww:ww:xx:xx:yy:yy:87:3e:56:2a:01:da usuario@dominio.com
The key's randomart image is:
+--[ RSA 2048]----+
| .o |
... (algunos renglones más)
| + o o |
| oo. |
+-----------------+

En el servidor

Del lado del servidor, estando como root, vamos a crear un usuario que se llame git.

root@prod:~# su -
root@prod:~# adduser git
Adding user `git' ...
Adding new group `git' (1000) ...
Adding new user `git' (1000) with group `git' ...
Creating home directory `/home/git' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for git
Enter the new value, or press ENTER for the default
Full Name []: git
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y

Una vez creado este nuevo usuario, vamos a hacer login como git, vamos a crear el directorio de .ssh en su home y vamos a crear un archivo que tendrá la lista de las llaves públicas de los usuarios que podrán acceder y poder conectarse.


root@prod:~# su git
git@prod:~$ pwd
/home/git
git@prod:~$ ls -l
total 0
git@prod:~$ pwd
/home/git
git@prod:~$ mkdir ~/.ssh && touch ~/.ssh/authorized_keys

Teniendo eso listo, vamos a tener que agregar al archivo ~/.ssh/authorized_keys la llave pública que generamos en nuestro ambiente local.

Aún en nuestro servidor, vamos a crear lo siguiente:

git@prod:~$ mkdir stage.git && cd stage.git
git@prod:~/stage.git$ git init
Initialized empty Git repository in /home/git/stage.git/.git/

En el comando de git init se podría agregar --bare, que lo que hace es que no se trae el código, solo el controlador de version de Git pero en mi caso, sí necesito el código ya que haré pruebas en stage y necesito correr el servidor con los nuevo cambios.

Otra vez en el ambiente local

En nuestro directorio con el código, vamos a crear un nuevo repositorio que llamaremos “stage” pero antes, vamos a enlistar los actuales de la siguiente manera:


[ arathvelazquez@Air:~/Code/webapp ] $ git remote -v
origin git@bitbucket.org:arathvelazquez/webapp.git (fetch)
origin git@bitbucket.org:arathvelazquez/webapp.git (push)

Creamos el nuevo repositorio

[ arathvelazquez@Air:~/Code/webapp ] $ git remote add stage ssh://git@dominio-webapp.com/home/git/stage.git
[ arathvelazquez@Air:~/Code/webapp ] $ git remote -v
origin git@bitbucket.org:arathvelazquez/webapp.git (fetch)
origin git@bitbucket.org:arathvelazquez/webapp.git (push)
stage ssh://git@dominio-webapp.com/home/git/stage.git (fetch)
stage ssh://git@dominio-webapp.com/home/git/stage.git (push)

Si fuera necesario actualizar la URL del nuevo repositorio, sería de la siguiente manera

[ arathvelazquez@Air:~/Code/webapp ] $ git remote set-url stage ssh://git@dominio-webapp.com/home/git/stage.git
[ arathvelazquez@Air:~/Code/webapp ] $ git remote -v
origin git@bitbucket.org:arathvelazquez/webapp.git (fetch)
origin git@bitbucket.org:arathvelazquez/webapp.git (push)
stage ssh://git@dominio-webapp.com/home/git/stage.git (fetch)
stage ssh://git@dominio-webapp.com/home/git/stage.git (push)

De ahora en adelante, si queremos hacer el push a “stage”, solo bastará con hacerlo de la siguiente manera:

[ arathvelazquez@Air:~/Code/webapp ] $ git push stage master

Reinstalando…

Ya tenía tiempo queriendo recuperar mi blog y creo que este es el mejor momento (aún y cuando eso de los blogs ya no está “in” y lo de ahora es el microblog o los vlogs), pero he estado trabajando en modo “solo o superhero” y ha habido mucho por hacer, crear, implementar y solucionar que creo necesario llevar un seguimiento para todo esto.

Y precisamente en estos momentos, necesito crear un stage para hacer un deployment de unos cambios radicales en una aplicación web y quería documentar los pasos para futuras referencias.