Add nginx ansible role blog article
This commit is contained in:
parent
cca6c4c2f8
commit
382e09e4f8
2 changed files with 337 additions and 1 deletions
336
content/blog/ansible/nginx-ansible-role.md
Normal file
336
content/blog/ansible/nginx-ansible-role.md
Normal file
|
@ -0,0 +1,336 @@
|
|||
---
|
||||
title: 'Nginx ansible role'
|
||||
description: 'The ansible role I use to manage my nginx web servers'
|
||||
date: '2024-10-28'
|
||||
tags:
|
||||
- ansible
|
||||
- nginx
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Before succumbing to nixos, I had been using an ansible role to manage my nginx web servers. Now that I am in need of it again I refined it a bit: here is the result.
|
||||
|
||||
## The role
|
||||
|
||||
### Vars
|
||||
|
||||
The role has OS specific vars in files named after the operating system. For example in `vars/Debian.yaml` I have:
|
||||
|
||||
``` yaml
|
||||
---
|
||||
nginx:
|
||||
etc_dir: '/etc/nginx'
|
||||
pid_file: '/run/nginx.pid'
|
||||
www_user: 'www-data'
|
||||
```
|
||||
|
||||
While in `vars/FreeBSD.yaml` I have:
|
||||
|
||||
``` yaml
|
||||
---
|
||||
nginx:
|
||||
etc_dir: '/usr/local/etc/nginx'
|
||||
pid_file: '/var/run/nginx.pid'
|
||||
www_user: 'www'
|
||||
```
|
||||
|
||||
### Tasks
|
||||
|
||||
The main tasks file setups nginx and the global configuration common to all virtual hosts:
|
||||
|
||||
``` yaml
|
||||
---
|
||||
- include_vars: '{{ ansible_distribution }}.yaml'
|
||||
|
||||
- name: 'Install nginx'
|
||||
package:
|
||||
name:
|
||||
- 'nginx'
|
||||
|
||||
- name: 'Make nginx vhost directory'
|
||||
file:
|
||||
path: '{{ nginx.etc_dir }}/vhost.d'
|
||||
mode: '0755'
|
||||
owner: 'root'
|
||||
state: 'directory'
|
||||
|
||||
- name: 'Deploy nginx configuration files'
|
||||
copy:
|
||||
src: '{{ item }}'
|
||||
dest: '{{ nginx.etc_dir }}/{{ item }}'
|
||||
notify: 'reload nginx'
|
||||
loop:
|
||||
- 'headers_base.conf'
|
||||
- 'headers_secure.conf'
|
||||
- 'headers_static.conf'
|
||||
- 'headers_unsafe_inline_csp.conf'
|
||||
|
||||
- name: 'Deploy nginx configuration template'
|
||||
template:
|
||||
src: 'nginx.conf'
|
||||
dest: '{{ nginx.etc_dir }}/'
|
||||
notify: 'reload nginx'
|
||||
|
||||
- name: 'Deploy nginx certificates'
|
||||
copy:
|
||||
src: '{{ item }}'
|
||||
dest: '{{ nginx.etc_dir }}/'
|
||||
notify: 'reload nginx'
|
||||
loop:
|
||||
- 'adyxax.org.fullchain'
|
||||
- 'adyxax.org.key'
|
||||
- 'dh4096.pem'
|
||||
|
||||
- name: 'Start nginx and activate it on boot'
|
||||
service:
|
||||
name: 'nginx'
|
||||
enabled: true
|
||||
state: 'started'
|
||||
```
|
||||
|
||||
I have a `vhost.yaml` task file which currently simply deploys a file and reload nginx:
|
||||
|
||||
``` yaml
|
||||
- name: 'Deploy {{ vhost.name }} vhost {{ vhost.path }}'
|
||||
template:
|
||||
src: '{{ vhost.path }}'
|
||||
dest: '{{ nginx.etc_dir }}/vhost.d/{{ vhost.name }}.conf'
|
||||
notify: 'reload nginx'
|
||||
```
|
||||
|
||||
### Handlers
|
||||
|
||||
There is a single `main.yaml` handler:
|
||||
|
||||
``` yaml
|
||||
---
|
||||
- name: 'reload nginx'
|
||||
service:
|
||||
name: 'nginx'
|
||||
state: 'reloaded'
|
||||
```
|
||||
|
||||
### Files
|
||||
|
||||
I deploy four configuration files in this role. These are all variants of the same theme and their purpose is just to prevent duplicating statements in the virtual hosts configuration files.
|
||||
|
||||
`headers_base.conf`:
|
||||
|
||||
``` nginx
|
||||
###############################################################################
|
||||
# \_o< WARNING : This file is being managed by ansible! >o_/ #
|
||||
# ~~~~ ~~~~ #
|
||||
###############################################################################
|
||||
|
||||
add_header X-Frame-Options deny;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header Referrer-Policy strict-origin;
|
||||
add_header Cache-Control no-transform;
|
||||
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
|
||||
# 6 months HSTS pinning
|
||||
add_header Strict-Transport-Security max-age=16000000;
|
||||
```
|
||||
|
||||
`headers_secure.conf`:
|
||||
|
||||
``` nginx
|
||||
###############################################################################
|
||||
# \_o< WARNING : This file is being managed by ansible! >o_/ #
|
||||
# ~~~~ ~~~~ #
|
||||
###############################################################################
|
||||
|
||||
include headers_base.conf;
|
||||
add_header Content-Security-Policy "script-src 'self'";
|
||||
```
|
||||
|
||||
`headers_static.conf`:
|
||||
|
||||
``` nginx
|
||||
###############################################################################
|
||||
# \_o< WARNING : This file is being managed by ansible! >o_/ #
|
||||
# ~~~~ ~~~~ #
|
||||
###############################################################################
|
||||
|
||||
include headers_secure.conf;
|
||||
# Infinite caching
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
```
|
||||
|
||||
`headers_unsafe_inline_csp.conf`:
|
||||
|
||||
``` nginx
|
||||
###############################################################################
|
||||
# \_o< WARNING : This file is being managed by ansible! >o_/ #
|
||||
# ~~~~ ~~~~ #
|
||||
###############################################################################
|
||||
|
||||
include headers_base.conf;
|
||||
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline'";
|
||||
```
|
||||
|
||||
### Templates
|
||||
|
||||
I have a single template for `nginx.conf`:
|
||||
|
||||
``` nginx
|
||||
###############################################################################
|
||||
# \_o< WARNING : This file is being managed by ansible! >o_/ #
|
||||
# ~~~~ ~~~~ #
|
||||
###############################################################################
|
||||
|
||||
user {{ nginx.www_user }};
|
||||
worker_processes auto;
|
||||
pid {{ nginx.pid_file }};
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
types_hash_max_size 4096;
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
gzip_vary on;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 256;
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types application/atom+xml application/geo+json application/javascript application/json application/ld+json application/manifest+json application/rdf+xml application/vnd.ms-fontobject application/wasm application/x-rss+xml application/x-web-app-manifest+json application/xhtml+xml application/xliff+xml application/xml font/collection font/otf font/ttf image/bmp image/svg+xml image/vnd.microsoft.icon text/cache-manifest text/calendar text/css text/csv text/javascript text/markdown text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/xml;
|
||||
|
||||
proxy_redirect off;
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header "Connection" "";
|
||||
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;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Server $host;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
client_max_body_size 40M;
|
||||
server_tokens off;
|
||||
default_type application/octet-stream;
|
||||
access_log /var/log/nginx/access.log;
|
||||
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param QUERY_STRING $query_string;
|
||||
fastcgi_param REQUEST_METHOD $request_method;
|
||||
fastcgi_param CONTENT_TYPE $content_type;
|
||||
fastcgi_param CONTENT_LENGTH $content_length;
|
||||
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_param DOCUMENT_URI $document_uri;
|
||||
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||
fastcgi_param REQUEST_SCHEME $scheme;
|
||||
fastcgi_param HTTPS $https if_not_empty;
|
||||
|
||||
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
|
||||
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||
fastcgi_param REMOTE_PORT $remote_port;
|
||||
fastcgi_param REMOTE_USER $remote_user;
|
||||
fastcgi_param SERVER_ADDR $server_addr;
|
||||
fastcgi_param SERVER_PORT $server_port;
|
||||
fastcgi_param SERVER_NAME $server_name;
|
||||
|
||||
# PHP only, required if PHP was built with --enable-force-cgi-redirect
|
||||
fastcgi_param REDIRECT_STATUS 200;
|
||||
|
||||
uwsgi_param QUERY_STRING $query_string;
|
||||
uwsgi_param REQUEST_METHOD $request_method;
|
||||
uwsgi_param CONTENT_TYPE $content_type;
|
||||
uwsgi_param CONTENT_LENGTH $content_length;
|
||||
|
||||
uwsgi_param REQUEST_URI $request_uri;
|
||||
uwsgi_param PATH_INFO $document_uri;
|
||||
uwsgi_param DOCUMENT_ROOT $document_root;
|
||||
uwsgi_param SERVER_PROTOCOL $server_protocol;
|
||||
uwsgi_param REQUEST_SCHEME $scheme;
|
||||
uwsgi_param HTTPS $https if_not_empty;
|
||||
|
||||
uwsgi_param REMOTE_ADDR $remote_addr;
|
||||
uwsgi_param REMOTE_PORT $remote_port;
|
||||
uwsgi_param SERVER_PORT $server_port;
|
||||
uwsgi_param SERVER_NAME $server_name;
|
||||
|
||||
ssl_dhparam dh4096.pem;
|
||||
ssl_session_cache shared:SSL:2m;
|
||||
ssl_session_timeout 1h;
|
||||
ssl_session_tickets off;
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
access_log off;
|
||||
server_name_in_redirect off;
|
||||
return 444;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name _;
|
||||
access_log off;
|
||||
server_name_in_redirect off;
|
||||
return 444;
|
||||
ssl_certificate adyxax.org.fullchain;
|
||||
ssl_certificate_key adyxax.org.key;
|
||||
}
|
||||
|
||||
include vhost.d/*.conf;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage example
|
||||
|
||||
I do not call the role from a playbook, I prefer running the setup from an application's role that relies on nginx using a `meta/main.yaml` containing something like:
|
||||
|
||||
``` yaml
|
||||
---
|
||||
dependencies:
|
||||
- role: 'borg'
|
||||
- role: 'nginx'
|
||||
- role: 'postgresql'
|
||||
```
|
||||
|
||||
Then from a tasks file:
|
||||
|
||||
``` yaml
|
||||
- include_role:
|
||||
name: 'nginx'
|
||||
tasks_from: 'vhost'
|
||||
vars:
|
||||
vhost:
|
||||
name: 'www'
|
||||
path: 'roles/www.adyxax.org/files/nginx-vhost.conf'
|
||||
```
|
||||
|
||||
I did not find an elegant way to pass a file path local to one role to another. Because of that, here I just specify the full vhost file path complete with the `roles/` prefix.
|
||||
|
||||
### Conclusion
|
||||
|
||||
I you have an elegant idea for passing the local file path from one role to another do not hesitate to ping me!
|
|
@ -224,7 +224,7 @@ I do not call the role from a playbook, I prefer running the setup from an appli
|
|||
``` yaml
|
||||
---
|
||||
dependencies:
|
||||
- role: 'borg
|
||||
- role: 'borg'
|
||||
- role: 'postgresql'
|
||||
```
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue