nginx Konfiguration mit Ansible
Mittlerweile haben sich auf meinem Server einige Domains und Subdomains angesammelt. Und spätestens seit ich die meisten Dienste über Docker-Container bereitstelle ist meine Nginx-Konfiguration relativ üppig geworden.
Vor einiger Zeit habe ich mir daher ein Ansible-Playbook erstellt, welches mir die Konfiguration etwas vereinfacht.
Kernstück ist, wie immer, die main.yml
:
- hosts: scn.21x9.org
tasks:
- include_vars: vhosts.yml
- include_vars: error_pages.yml
- name: "Install nginx"
apt: name=nginx state=present
notify:
- "Restart nginx"
- name: "Generate global config"
template:
src: ./nginx.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: 0644
notify:
- "Restart nginx"
- name: "Generate default config"
template:
src: ./default.j2
dest: /etc/nginx/sites-available/default.conf
owner: root
group: root
mode: 0644
notify:
- "Restart nginx"
- name: "Generate vhosts config"
template:
src: ./vhosts.j2
dest: /etc/nginx/sites-available/{{ item['name'] }}.conf
owner: root
group: root
mode: 0644
notify:
- "Restart nginx"
with_items:
- "{{ nginx_vhosts }}"
- name: "Create symlinks"
file:
src: /etc/nginx/sites-available/{{ item['name'] }}.conf
dest: /etc/nginx/sites-enabled/{{ item['name'] }}.conf
state: link
with_items:
- {name: 'default'}
- "{{ nginx_vhosts }}"
register: managed_files
- set_fact:
nginx_confs: "{{ managed_files.results|selectattr('dest', 'string')|map(attribute='dest')|list + managed_files.results|selectattr('path', 'string')|map(attribute='path')|select|list }}"
- shell: ls -d -1 /etc/nginx/sites-enabled/*
register: contents
- name: "Remove unmanaged symlinks"
file: path={{ item }} state=absent
with_items: "{{ contents.stdout_lines }}"
when: nginx_confs and item not in nginx_confs
notify:
- "Restart nginx"
- name: "Generate error pages"
template:
src: ./error_page.j2
dest: /var/www/html/{{ item['type'] }}.html
owner: root
group: root
mode: 0644
with_items:
- "{{ error_pages }}"
handlers:
- name: "Restart nginx"
shell: /usr/sbin/nginx -t && /etc/init.d/nginx reload
Wann immer eine Konfigurationsdatei geändert wird, wird der Handler Restart nginx
ausgeführt. Dieser führt zunächst mittels nginx -t
einen Test der entstandenen Konfiguration durch, nur wenn diese keine Fehler aufweist, wird die Konfiguration neu eingelesen.
Welche Konfigurationen geschrieben werden steuert die Datei vhosts.yml
:
nginx_vhosts:
- { name: "21x9.org", alias: "www.21x9.org", port: "8000", extra: 'wordpress.j2', hsts: "on", hpkp: "on", security: "on", noref: "on", spdy: "on" }
- { name: "legacy.21x9.org", alias: "www.legacy.21x9.org", port: "8002", extra: 'legacy.j2', hsts: "off", hpkp: "off", security: "off", noref: "off", ip: "1.2.3.4" }
- { name: "searx.xyz", alias: "www.searx.xyz ipv4.searx.xyz ipv6.searx.xyz", port: "8888", extra: 'searx.j2', hsts: "on", hpkp: "on", security: "on", noref: "on", spdy: "on" }
- { name: "stats.searx.xyz", alias: "none", hsts: "on", hpkp: "on", security: "on", noref: "on", spdy: "on", root: "/var/www/html/stats/" }
- { name: "pukiyama-kakuzo.com", alias: "www.pukiyama-kakuzo.com", hsts: "on", hpkp: "on", security: "on", noref: "on", spdy: "on", root: "/var/www/html/pukiyama-kakuzo/" }
Für jeden Eintrag in der yhosts.yml
erzeugt das Playbook eine eigene Nginx-Configdatei auf Basis eine Standardtemplates (vhosts.j2
).
###############################################################################
### {{ item['name'] }}
###############################################################################
server {
{% if 'ip' in item %}
listen {{ item['ip']}}:80;
{% else %}
listen 80;
listen [::]:80;
{% endif %}
{% if item['alias'] != "none" %}
server_name {{ item['name'] }}
{{ item['alias'] }};
{% else %}
server_name {{ item['name'] }};
{% endif %}
{% if 'root' in item %}
root {{ item['root'] }};
{% else %}
root /var/www/html;
{% endif %}
# For letsencrypt.sh
location /.well-known/acme-challenge/ {
root /var/www/html/;
try_files $uri $uri/ =404;
}
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://{{ item['name'] }}$request_uri;
access_log off;
error_log syslog,tag=nginx,severity=error,facility=local7;
}
server {
{% if 'ip' in item %}
listen {{ item['ip']}}:443 ssl{% if 'spdy' in item %} http2{% endif %};
{% else %}
listen 443 ssl{% if 'spdy' in item %} http2{% endif %};
listen [::]:443 ssl{% if 'spdy' in item %} http2{% endif %};
{% endif %}
{% if item['alias'] != "none" %}
server_name {{ item['name'] }}
{{ item['alias'] }};
{% else %}
server_name {{ item['name'] }};
{% endif %}
gzip off;
{% if 'ssl' in item %}
ssl_certificate /etc/letsencrypt.sh/certs/{{ item['ssl'] }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt.sh/certs/{{ item['ssl'] }}/privkey.pem;
#ssl_trusted_certificate /etc/letsencrypt.sh/certs/{{ item['ssl'] }}/fullchain.pem;
{% else %}
ssl_certificate /etc/letsencrypt.sh/certs/{{ item['name'] }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt.sh/certs/{{ item['name'] }}/privkey.pem;
#ssl_trusted_certificate /etc/letsencrypt.sh/certs/{{ item['name'] }}/fullchain.pem;
{% endif %}
ssl_stapling on;
ssl_stapling_verify on;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'AES256+EECDH:AES256+EDH:!aNULL';
ssl_ecdh_curve secp384r1;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
server_tokens off;
access_log /var/log/nginx/{{ item['name'] }}.log extended;
error_log /var/log/nginx/{{ item['name'] }}.log;
{% if 'root' in item %}
root {{ item['root'] }};
{% else %}
root /var/www/html;
{% endif %}
# For letsencrypt.sh
location /.well-known/acme-challenge/ {
root /var/www/html/;
try_files $uri $uri/ =404;
}
location ~ /\.ht {
deny all;
}
{% for error in error_pages %}
{% if error['type'] == '401' %}
error_page {{ error['type'] }} /{{ error['type'] }}.html;
{% else %}
error_page {{ error['type'] }} /{{ error['type'] }}.html;
{% endif %}
{% endfor %}
{% for error in error_pages %}
location /{{ error['type'] }}.html {
root /var/www/html;
internal;
break;
}
{% endfor %}
location / {
{% if 'maintenance' in item %}
set $maintenance true;
{% else %}
set $maintenance false;
{% endif %}
if (-f $document_root/maintenance.html) {
set $maintenance true;
}
if ($maintenance = true) {
rewrite / /503.html;
}
{% if 'port' in item %}
proxy_pass http://localhost:{{ item['port'] }}/;
proxy_redirect off;
proxy_intercept_errors on;
proxy_read_timeout 86400;
proxy_ignore_client_abort on;
proxy_connect_timeout 120s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_headers_hash_max_size 512;
proxy_buffering on;
proxy_cache_bypass $http_pragma $http_authorization $cookie_nocache;
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_set_header X-Scheme $scheme;
proxy_hide_header Strict-Transport-Security;
proxy_hide_header Public-Key-Pins;
proxy_hide_header Content-Security-Policy;
proxy_hide_header X-Content-Security-Policy;
proxy_hide_header X-WebKit-CSP;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-XSS-Protection;
proxy_hide_header X-Frame-Options;
proxy_hide_header Alternate-Protocol;
proxy_hide_header X-Powered-By;
add_header Front-End-Https on;
{% endif %}
{% if 'hsts' in item %}
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
{% endif %}
{% if 'hpkp' in item %}
add_header Public-Key-Pins "pin-sha256=\"YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=\"; pin-sha256=\"sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=\"; pin-sha256=\"dd9ClLS4xSTx8j2dH5/hjS6dPXTkzpzDcKeEX+3J74E=\"; pin-sha256=\"NO6gHdYxKGz47EYVvoXpJhxY3RXfoM1O56gPrOGmrg8=\"; max-age=7776000; includeSubDomains;";
{% endif %}
{% if 'security' in item %}
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
{% endif %}
{% if 'noref' in item %}
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
{% endif %}
{% if 'extra' in item %}
{% include item['extra'] %}
{% else %}
}
{% endif %}
}
Je nachdem wie der jeweilige vHost in der vhosts.yml
angelegt ist, wird also entweder eine Reverse-Proxy Konfiguration erzeugt, oder Nginx selbst dient als Webserver. Ist letzteres der Fall, kann für den vHost der Wert root
gesetzt werden. Er legt dann das docroot des jeweiligen vHosts fest.
Die Option spdy
in der vhosts.yml
hätte ich mir im Grunde sparen können, denn wenn die Option für eine IP eingeschaltet ist, gilt sie für alle anderen vHost auf der gleichen IP gleichermaßen. Das ist (in meinem use-case) auch der Grund, warum man überhaupt in der vhosts.yml
eine weitere IP angeben kann, für vHosts, die eben kein SPDY unterstützen sollen.
Ansonsten kann man für jeden vHost wählen, ob folgende Optionen verwendet werden sollen:
- hsts:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
- hpkp:
add_header Public-Key-Pins "pin-sha256=\"YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=\"; pin-sha256=\"sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=\"; pin-sha256=\"dd9ClLS4xSTx8j2dH5/hjS6dPXTkzpzDcKeEX+3J74E=\"; pin-sha256=\"NO6gHdYxKGz47EYVvoXpJhxY3RXfoM1O56gPrOGmrg8=\"; max-age=7776000; includeSubDomains;";
- security:
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
- noref:
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
- maintenance: Leitet die Anfragen auf die
503
-Fehlerseite um
Da damit natürlich noch längst nicht alle Konfigurationsmöglichkeiten ausgeschöpft sind, kann man für jeden vHost noch eine Datei mit weiteren Einstellungen an die generierte Konfiguration angehängt werden (extra
). Hier Beispielhaft das extra
-File für searx
:
add_header Content-Security-Policy "default-src 'none'; font-src 'self'; script-src 'self' 'sha256-2XPtiIriLCRC9BnusBjoTHsfh5zn7F+h7Cr17XwSN50='; img-src * data:; object-src 'none'; style-src 'self' 'sha256-PEo9RAB0C7Pw0EzsgS+HuVOHFPZRr93dZNJ9mYzz95A=' 'sha256-lrhGtO62QsLjBNsk1WGpvRZbHmXYMG/CmjhMFhLDJpg=' 'sha256-4SHcbKIX1Fx5rhlottWqqHggM5MSJz+xg9uwwq8UK74=' 'sha256-bQfd5MwpJ1ZxnVh373acxlqXQ8VwM88QhEHNuQ0ppNg=' 'sha256-yIJ6JcbosmUis6IDdnDV7NU2qPkU/1KUJHRwj44Q32o='; frame-src 'self'; connect-src 'self'; form-action 'self'; base-uri 'self';frame-ancestors 'self'; report-uri https://searx.report-uri.com/r/d/csp/enforce;";
add_header X-Content-Security-Policy "default-src 'none'; font-src 'self'; script-src 'self' 'sha256-2XPtiIriLCRC9BnusBjoTHsfh5zn7F+h7Cr17XwSN50='; img-src * data:; object-src 'none'; style-src 'self' 'sha256-PEo9RAB0C7Pw0EzsgS+HuVOHFPZRr93dZNJ9mYzz95A=' 'sha256-lrhGtO62QsLjBNsk1WGpvRZbHmXYMG/CmjhMFhLDJpg=' 'sha256-4SHcbKIX1Fx5rhlottWqqHggM5MSJz+xg9uwwq8UK74=' 'sha256-bQfd5MwpJ1ZxnVh373acxlqXQ8VwM88QhEHNuQ0ppNg=' 'sha256-yIJ6JcbosmUis6IDdnDV7NU2qPkU/1KUJHRwj44Q32o='; frame-src 'self'; connect-src 'self'; form-action 'self'; base-uri 'self';frame-ancestors 'self'; report-uri https://https://searx.report-uri.com/r/d/csp/enforce;";
add_header X-WebKit-CSP "default-src 'none'; font-src 'self'; script-src 'self' 'sha256-2XPtiIriLCRC9BnusBjoTHsfh5zn7F+h7Cr17XwSN50='; img-src * data:; object-src 'none'; style-src 'self' 'sha256-PEo9RAB0C7Pw0EzsgS+HuVOHFPZRr93dZNJ9mYzz95A=' 'sha256-lrhGtO62QsLjBNsk1WGpvRZbHmXYMG/CmjhMFhLDJpg=' 'sha256-4SHcbKIX1Fx5rhlottWqqHggM5MSJz+xg9uwwq8UK74=' 'sha256-bQfd5MwpJ1ZxnVh373acxlqXQ8VwM88QhEHNuQ0ppNg=' 'sha256-yIJ6JcbosmUis6IDdnDV7NU2qPkU/1KUJHRwj44Q32o='; frame-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self';frame-ancestors 'self'; report-uri https://searx.report-uri.com/r/d/csp/enforce;";
add_header Expect-CT "max-age=0; report-uri=https://searx.report-uri.com/r/d/ct/reportOnly;";
add_header Expect-Staple "max-age=0; report-uri=https://searx.report-uri.com/r/d/staple/reportOnly";
}
location /stats {
rewrite / https://stats.searx.xyz;
}
Hauptsächlich werden hier CSP-Header (Content-Security-Policy) gesetzt, da diese für jede Seite ziemlich einzigartig sind, wenn man sie sehr strikt definieren möchte. Aber auch für Weiterleitungen oder sonstige zusätzliche location
-Einträge nutzte ich die extra
-Files sehr gern.
Neben der Nginx-Konfiguration erzeugt das Playbook außerdem noch Fehlerseiten. Welche definiert die Datei error_pages.yml
:
error_pages:
- { type: "502", title: "Error 502: Bad Gateway", message: "<h1>Error 502: Bad Gateway</h1><p>Most of the time, a 502 error occurs because the server is too busy or because there's maintenance being performed on it.</p><p><b>Please check back in a few minutes.</b></p>" }
- { type: "503", title: "Error 503: Service unavailable", message: "<h1>Error 503: Service unavailable</h1><p>Most of the time, a 503 error occurs because there's maintenance being performed.</p><p><b>Please check back in a few minutes.</b></p>" }
- { type: "401", title: "Error 401: Unauthorized", message: "<h1>Error 401: Unauthorized</h1><p>You are not allowed to access this page.</p><p><b>Please log in to access this page.</b></p>" }
- { type: "403", title: "Error 403: Forbidden", message: "<h1>Error 403: Forbidden</h1><p>You don't have permission to access this page or directory on this server</p>" }
- { type: "404", title: "Error 404: File not found", message: "<h1>Error 404: File not found</h1><p>The requested URL was not found on this server. Maybe it was a valid URL once, but the file/directory has been removed.</p><p><b>Please double-check the URL and try again.</b></p>" }
Die so definierten Meldungen werden in das Template error_page.j2
geschrieben:
<html>
<head>
<title>{{ item['title'] }}</title>
<style type="text/css">
h1 {
font-family: Verdana, sans-serif;
font-size: 14pt;
}
p, ul, li, td {
font-family: Verdana, sans-serif;
font-size: 9pt;
line-height: 175%;
}
a {
color: rgb(31, 83, 81);
}
</style>
</head>
<body>
<table cellspacing="0" cellpadding="0" border="0" width="100%" height="100%">
<tr height="100%">
<td height="100%" width="100%" align="center">
<table cellspacing="0" cellpadding="0" border="0" width="830">
<tr>
<td><img src="data:image/gif;base64,R0lGODlhGQAZAPQeAOvr6/Dw8Nzc3PX19eTk5Pb29vLy8vf39/Hx8eXl5fT09PPz8+3t7ezs7Pj4+Pr6+v39/d/f3+np6fn5+e/v7+fn5+Dg4N3d3fv7++Li4ubm5ujo6Pz8/P///wAAAAAAACH5BAUAAB4ALAAAAAAZABkAAAWgoAdBHPZMznEUxeC+8Dua06oshoEgQe//PYjJUVAYAhRGA8BsOp2cB3ERYAAkFU2CwO16u5hJgdrYJDKWyEXAbrvbj4Mi0OjY7/h8XmxgbPSAgA5zAAmBh3cHCxQSGYiIB30VFo+HBQYNGhGVgQUIhRecgAOfBAKiegMBAKaoeaqsp653sK2zdrWyt7m3uKu2u7+6s7y9xcGxvR3HxMLKIQA7" /></td>
<td style="background-image:url('data:image/gif;base64,R0lGODdhKQAPAPIAAOTk5PX19dzc3PDw8Ovr6////wAAAAAAACwAAAAAKQAPAAADOBi63P6wjUmrvThbwrv/YCh+QGmeaKquqOC+cCzPdFzceK7vfO//wKBwSCwaj8ikcslsOp/QaC4BADs=');background-repeat:repeat-x;" colspan="2"> </td>
<td><img src="data:image/gif;base64,R0lGODdhGQAZAPUAAOvr6+Tk5Nzc3PDw8PX19cvLy9PT0/b29uXl5e3t7ezs7PLy8uLi4vf399/f3/T09Ofn5+/v7/j4+Onp6dXV1fPz8+bm5vHx8fr6+uDg4NnZ2d3d3ejo6Pn5+c/Pz83NzdTU1NbW1tLS0tvb2/v7+8zMzN7e3v39/fz8/P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAGQAZAAAGo0CCcEgsHg6NhqSDIaFOg6h0Or1cFovKA8kkAb7gsBigSEQGi60EE2i733CEBTIBJAaVg0TA7/v/Gw4ZDAgcCngHKYqLjI2NDBCHD46UlCYMHAkLlZyLGg4IAAOdnRQjDBMRpJwiGhkQCauVHiEOFgqylB8UG6G5jiUgAgEAv40FBsPFxovIyszNycTQis7T1NbL0NnUKdzY0trM39vh3eTj4UEAOw==" /></td>
</tr>
<tr>
<td style="background-image:url('data:image/gif;base64,R0lGODdhIgAhAPIAAPDw8PX19dzc3OTk5Ovr6////wAAAAAAACwAAAAAIgAhAAADUBhAI/UwykmLYq7qbVfj4HR9YdlhZjlmKbe24Atv8lzVtuixuYT3kB/wRBoGd8aIELjsNXNPW3Q2hVVb11TWtFUhk49uSBz7gskucxJNUxsTADs=');background-repeat:repeat-y;"> </td>
<td height="50" colspan="2"> </td>
<td style="background-image:url('data:image/gif;base64,R0lGODdhIgAhAPIAANzc3OTk5MvLy9PT0+vr6////wAAAAAAACwAAAAAIgAhAAADUVi63P5FDBAIvFhJarNvW/WN2iSSX9ihmcp67ovFMkTXzo0z+l5yvpxpFfydikZisedj7pw4aE0qo76sLCxKS+KOvKkhcgGGiceR87jcUiNVCQA7');background-position:right;background-repeat:repeat-y;"> </td>
</tr>
<tr>
<td style="background-image:url('data:image/gif;base64,R0lGODdhIgAhAPIAAPDw8PX19dzc3OTk5Ovr6////wAAAAAAACwAAAAAIgAhAAADUBhAI/UwykmLYq7qbVfj4HR9YdlhZjlmKbe24Atv8lzVtuixuYT3kB/wRBoGd8aIELjsNXNPW3Q2hVVb11TWtFUhk49uSBz7gskucxJNUxsTADs=');background-repeat:repeat-y;"> </td>
<td valign="top" width="80"><img style="margin-top:-15px;margin-right:25px;margin-left:8px;" src="data:image/gif;base64,R0lGODlhNQA1APZfAPz8/Pj4+ODg4NfX19nZ2fv7+/r6+tbW1tTU1N7e3tzc3OHh4dbW1dXV1dLS0tPT08zMzNvb29jY2Pb29tjY1/n5+cvLysrKytDQ0PLy8uPj49HR0czMy93d3fHx8crKydPT0uzs7M/Pz+Tk5M3Nze7u7tHR0MnJyM7Ozurq6vDw8M7Ozd3d3MjIyO/v7+bm5vPz8+fn5/T09Onp6cfHx+3t7evr6+jo6PX19eXl5ePj4vf399fX1tnZ2NLS0c3NzNTU09/f3snJyf7+/tXV1MvLy+3t7PDw79ra2ebm5cjIx+Dg3+Hh4M/Pzvz8+97e3dDQz+/v7vHx8OTk4+jo5+7u7dvb2tzc2+vr6vLy8dra2uLi4t/f3/39/f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4zLWMwMTEgNjYuMTQ1NjYxLCAyMDEyLzAyLzA2LTE0OjU2OjI3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOkVERUJFMUQ5QTQyNkUzMTE5QTVCRUQ1NzA2RjdCNTM5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkYxRjAyNjgwMjc4ODExRTM4QTY0OTgwOTdCQkU0OTA3IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkYxRjAyNjdGMjc4ODExRTM4QTY0OTgwOTdCQkU0OTA3IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6N0VFNzAxMjk0RDI3RTMxMUI5QTVERDU5MkE4RTg5MjgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RURFQkUxRDlBNDI2RTMxMTlBNUJFRDU3MDZGN0I1MzkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQFAABfACwAAAAANQA1AAAH/4BfgoOEhYZfXYmKioeNjo+GXQAFBhUVAZiYlgYFAF2QoI+SBZcTMjAZHiqrHhkwMhMBFZ2fobaSBgETMCEvWwkKEQTDSAoCMTUqMLEGnraQkwE4Li8CCR0EFDzbA90DBwgMCi8lGTgBnc+HkhUTLjkLCRQPDiAPCA0MBwMUEsMDRBwMGFEjw4QKztQJ6lIgAIwbOhIgwGBiQ717+fb1I6BFi7AHGCTccCEDXa1nXQzsUJFjCwMUIihatIdPHz9/HSMoYNFjw4YtITLsMHASVMoJNXIkELECpsyLNTXi9LizQ4cHTRTY8DCBaKijNV5o+UGiacyKUDPe5EiVRYcEQf8akNCSgqtXUQV2lIghgQOEsk7R0lS7MWfVBFy4HIAQoe6OAkUNAQjg4YYWC35JEFj6dLDNwm1/JRbQAEIHGxkCAHAEoMLDJRcwQyDQLEhnjJ+n6izhJcfoBRt+AJWBcF2BCb0gxObQw4AXLwC4CMYtlW0E3s99C1ggAIKDFy4mQJZMeQaCD7ElOH8OnctM6ms9YmefY/sWAh8UzEi9mlBKHFhoIAR6FkwxBHvPRZdWbhzNx94MQSywxRYioEAQDncJMpkHKfhwAoEcbHEggtF5JpUWDj4XQgL2baHACVrspxohx4WQRAsfLgeBiAh6UQAX8FGAhAs9erEiFy1q8AP/BiOUIN4gXVQgQwoU4AjiX1t00eOPUQ1AAJE9htDBbxNqMMAFXKQAQwW1TOaCDRBYqWNZWW7JRUYSgImgmIghKeEWGgjQggQxqDDjF8fVEAMNcsoGWJ0I/shAnkUaocBbZAI6ggUYaOBkAYIYIMOKjOboqFmQslfAAkdUeoVbfSY5ggMcLBCCDAYg4poNPZR6JWAxpfqcE0XWoBOsmWowwgEXJKAmm1FmEAIQvs5pFkXCFhvBsZj6WeYIWiihwA0ZQEtZCD5Ue2pgP2lZpBc1GIast5oqQEMEMXgQQCIBqBACFOpmdi1aI4yIYAlIyNutrB3QoMULhvLrb7qNCswu/xFSvFuAAArH+qey9j4ccRf9ThvwXwMjkPG7PnLMrcffhivyviR7wOvJwGIAhAcsqyrAy8kuKwS++iZSQQY2JICzWQ/w3KMLAPRogACHBe3ABeOWazQMNixaMcooOOD0wQPoEDWCU88rqwUoOLvmJ6KGAOfXZYldZAkH6GO21AIs/LGgD9iKqyDHlUAl3T5kUWQUB6i1N9rWJDuAEBJ4+uQXk6kww42m+rVBBos30CUFj7M3dbI/MFbooVHCkEKHnZsA+tNEBClB6c8ZwAS9LzqQ5tuDFH7DFgPGhsHsCLqAgIlraXC26Uy02EQRBDR5uYYBZDCDeQSmUKQLQCxYnf8WGhCLoBER3veBAwnI2N9CBuCg6AvKWSCCgy488N7oU+lQAHsyEMB2ujObC2UIe9qLAWwwAwXsHAEE0+GfdTTwPxz8ZgNFaAAX3BeJ47ggBlS4jF8wUIUjOOA2EjSMBjyQGCQ1oAgbGEd4xiMZKYUgB3zJTEXOsj/C6KZqioEABsh3q+I0YjJIG4FYyDKwHjKoY3EhAQYIsAXUHKoRDNkBhzSgFKawS3zxaYtVQLCCKS7AMTR0xFE4NKEFvISHYATNsXqAARE4QAtbqEtXIoNFA0zAZiMQgA6eMJEI+tA6BACJCA6gAA1sZY8oUYm0qsGFBTxhHnH0BwEGgAAHbOCsAQTgwguCMhQ+QoIhAZCBC2YwAi6MCRjZ2AYPvDEABiAgfAjYzAhmQBKTKGQhrZlABmrASi4oICdW0AIBelBLIuBDAhHgwi4LcpCE/FJDDcFBBgwXAw3EIxgdoUoCFqCBGKSgHOdIxzULgQtdwEAFJQhBCm4QgxfYMwY3SEEISqAMZlhzneycRClOkYpVqKAVr4jFLP4JUCxOohKXyIQsKsAJhjb0K4tYREMDAQA7" /></td>
<td width="750" style="padding-right:50px;">
{{ item['message'] }}
</td>
<td style="background-image:url('data:image/gif;base64,R0lGODdhIgAhAPIAANzc3OTk5MvLy9PT0+vr6////wAAAAAAACwAAAAAIgAhAAADUVi63P5FDBAIvFhJarNvW/WN2iSSX9ihmcp67ovFMkTXzo0z+l5yvpxpFfydikZisedj7pw4aE0qo76sLCxKS+KOvKkhcgGGiceR87jcUiNVCQA7');background-position:right;background-repeat:repeat-y;"> </td>
</tr>
<tr>
<td style="background-image:url('data:image/gif;base64,R0lGODdhIgAhAPIAAPDw8PX19dzc3OTk5Ovr6////wAAAAAAACwAAAAAIgAhAAADUBhAI/UwykmLYq7qbVfj4HR9YdlhZjlmKbe24Atv8lzVtuixuYT3kB/wRBoGd8aIELjsNXNPW3Q2hVVb11TWtFUhk49uSBz7gskucxJNUxsTADs=');background-repeat:repeat-y;"> </td>
<td height="40" colspan="2"> </td>
<td style="background-image:url('data:image/gif;base64,R0lGODdhIgAhAPIAANzc3OTk5MvLy9PT0+vr6////wAAAAAAACwAAAAAIgAhAAADUVi63P5FDBAIvFhJarNvW/WN2iSSX9ihmcp67ovFMkTXzo0z+l5yvpxpFfydikZisedj7pw4aE0qo76sLCxKS+KOvKkhcgGGiceR87jcUiNVCQA7');background-position:right;background-repeat:repeat-y;"> </td>
</tr>
<tr>
<td><img src="data:image/gif;base64,R0lGODdhGQAZAPUAAOvr6+Tk5Nzc3PDw8PX19cvLy9PT0/b29uXl5e3t7ezs7PLy8uLi4vf399/f3/T09Ofn5+/v7/j4+Onp6dXV1fPz8+bm5vHx8fr6+uDg4NnZ2d3d3ejo6Pn5+c/Pz83NzdTU1NbW1tLS0tvb2/v7+8zMzN7e3v39/fz8/P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAGQAZAAAGokDCABAQpI7IpFIpJBqX0GWzGK0ip09rFKutcrvQL5g5pI7JzjParD6K2291/DwfEy7p9vGAR2z0ewsKFg6AKQ0LCRAZhg0VERMMhhIPQwiGHQeJHAwmGhQiHh8lBaWmp6UYDZUKEAwOIxohFCAGtre4tiSZFQMKHAgMGQ4bAsbHyMcoGBIHvQkAExAWCAHW19jXJyTMBw8LAxEJCgDl5ufnQQA7" /></td>
<td style="background-image:url('data:image/gif;base64,R0lGODdhKQAPAPIAAMvLy9PT0+Tk5Nzc3Ovr6////wAAAAAAACwAAAAAKQAPAAADOFi63P4wykmrvTjrzbv/YCiOJAOcaKqubKsGcCzPdG3PQ67vfO//PIFwSCwaj8giYclsOp/QqDMBADs=');background-position:bottom;background-repeat:repeat-x;" colspan="2"> </td>
<td><img src="data:image/gif;base64,R0lGODdhGQAZAPUAAOvr6+Tk5Nzc3MvLy9PT0+Li4vDw8NXV1dnZ2eXl5e3t7ezs7N/f3+fn59vb293d3dLS0s/Pz+/v7/Ly8unp6dbW1szMzM3Nzebm5uDg4NTU1Ojo6PT09Pb29t7e3vPz8/j4+Pr6+v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAGQAZAAAGn0CRcEgsFgcEQQBgbDaRSqZzKoQuqVSrFPtMXrndKDj8HRO15qN3mxah29U1PC6ev+H3tkVTh18ODwlsaREVDBgLcyIQCBkNCooHDgUUEooIDIIGA5ydnp4WFxEQBwgeBRsKEwSsra6uGgcVCA4MBQ0LBhwCvL2+vw8MGQUJG7kfHQHKy8zNCRgNFAAKBsggANjZ2tsACwoSBhMcHSAhQQA7" /></td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
Führt man das Playbook mit ansible-playbook /opt/ansible/nginx/main.yml --diff
aus, werden somit schöne und leicht wartbare Nginx-Configs erzeugt.
Hier einmal die Konfiguration für pukiyama-kakuzo.com
, hier ist Nginx der Webserver:
###############################################################################
### pukiyama-kakuzo.com
###############################################################################
server {
listen 80;
listen [::]:80;
server_name pukiyama-kakuzo.com
www.pukiyama-kakuzo.com;
root /var/www/html/pukiyama-kakuzo/;
# For letsencrypt.sh
location /.well-known/acme-challenge/ {
root /var/www/html/;
try_files $uri $uri/ =404;
}
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://pukiyama-kakuzo.com$request_uri;
access_log off;
error_log syslog,tag=nginx,severity=error,facility=local7;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name pukiyama-kakuzo.com
www.pukiyama-kakuzo.com;
gzip off;
ssl_certificate /etc/letsencrypt.sh/certs/pukiyama-kakuzo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt.sh/certs/pukiyama-kakuzo.com/privkey.pem;
#ssl_trusted_certificate /etc/letsencrypt.sh/certs/pukiyama-kakuzo.com/fullchain.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'AES256+EECDH:AES256+EDH:!aNULL';
ssl_ecdh_curve secp384r1;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
server_tokens off;
access_log /var/log/nginx/pukiyama-kakuzo.com.log extended;
error_log /var/log/nginx/pukiyama-kakuzo.com.log;
root /var/www/html/pukiyama-kakuzo/;
# For letsencrypt.sh
location /.well-known/acme-challenge/ {
root /var/www/html/;
try_files $uri $uri/ =404;
}
location ~ /\.ht {
deny all;
}
error_page 502 /502.html;
error_page 503 /503.html;
error_page 401 /401.html;
error_page 403 /403.html;
error_page 404 /404.html;
location /502.html {
root /var/www/html;
internal;
break;
}
location /503.html {
root /var/www/html;
internal;
break;
}
location /401.html {
root /var/www/html;
internal;
break;
}
location /403.html {
root /var/www/html;
internal;
break;
}
location /404.html {
root /var/www/html;
internal;
break;
}
location / {
set $maintenance false;
if (-f $document_root/maintenance.html) {
set $maintenance true;
}
if ($maintenance = true) {
rewrite / /503.html;
}
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header Public-Key-Pins "pin-sha256=\"YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=\"; pin-sha256=\"sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=\"; pin-sha256=\"dd9ClLS4xSTx8j2dH5/hjS6dPXTkzpzDcKeEX+3J74E=\"; pin-sha256=\"NO6gHdYxKGz47EYVvoXpJhxY3RXfoM1O56gPrOGmrg8=\"; max-age=7776000; includeSubDomains;";
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
}
}
Wie gesagt, nutze ich Nginx auf oft als Reverse-Proxy, daher hier beispielhaft die Konfiguration für searx.xyz
:
###############################################################################
### searx.xyz
###############################################################################
server {
listen 80;
listen [::]:80;
server_name searx.xyz
www.searx.xyz ipv4.searx.xyz ipv6.searx.xyz;
root /var/www/html;
# For letsencrypt.sh
location /.well-known/acme-challenge/ {
root /var/www/html/;
try_files $uri $uri/ =404;
}
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://searx.xyz$request_uri;
access_log off;
error_log syslog,tag=nginx,severity=error,facility=local7;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name searx.xyz
www.searx.xyz ipv4.searx.xyz ipv6.searx.xyz;
gzip off;
ssl_certificate /etc/letsencrypt.sh/certs/searx.xyz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt.sh/certs/searx.xyz/privkey.pem;
#ssl_trusted_certificate /etc/letsencrypt.sh/certs/searx.xyz/fullchain.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'AES256+EECDH:AES256+EDH:!aNULL';
ssl_ecdh_curve secp384r1;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
server_tokens off;
access_log /var/log/nginx/searx.xyz.log extended;
error_log /var/log/nginx/searx.xyz.log;
root /var/www/html;
# For letsencrypt.sh
location /.well-known/acme-challenge/ {
root /var/www/html/;
try_files $uri $uri/ =404;
}
location ~ /\.ht {
deny all;
}
error_page 502 /502.html;
error_page 503 /503.html;
error_page 401 /401.html;
error_page 403 /403.html;
error_page 404 /404.html;
location /502.html {
root /var/www/html;
internal;
break;
}
location /503.html {
root /var/www/html;
internal;
break;
}
location /401.html {
root /var/www/html;
internal;
break;
}
location /403.html {
root /var/www/html;
internal;
break;
}
location /404.html {
root /var/www/html;
internal;
break;
}
location / {
set $maintenance false;
if (-f $document_root/maintenance.html) {
set $maintenance true;
}
if ($maintenance = true) {
rewrite / /503.html;
}
proxy_pass http://localhost:8888/;
proxy_redirect off;
proxy_intercept_errors on;
proxy_read_timeout 86400;
proxy_ignore_client_abort on;
proxy_connect_timeout 120s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_headers_hash_max_size 512;
proxy_buffering on;
proxy_cache_bypass $http_pragma $http_authorization $cookie_nocache;
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_set_header X-Scheme $scheme;
proxy_hide_header Strict-Transport-Security;
proxy_hide_header Public-Key-Pins;
proxy_hide_header Content-Security-Policy;
proxy_hide_header X-Content-Security-Policy;
proxy_hide_header X-WebKit-CSP;
proxy_hide_header X-Content-Type-Options;
proxy_hide_header X-XSS-Protection;
proxy_hide_header X-Frame-Options;
proxy_hide_header Alternate-Protocol;
proxy_hide_header X-Powered-By;
add_header Front-End-Https on;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header Public-Key-Pins "pin-sha256=\"YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=\"; pin-sha256=\"sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=\"; pin-sha256=\"dd9ClLS4xSTx8j2dH5/hjS6dPXTkzpzDcKeEX+3J74E=\"; pin-sha256=\"NO6gHdYxKGz47EYVvoXpJhxY3RXfoM1O56gPrOGmrg8=\"; max-age=7776000; includeSubDomains;";
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
add_header Content-Security-Policy "default-src 'none'; font-src 'self'; script-src 'self' 'sha256-2XPtiIriLCRC9BnusBjoTHsfh5zn7F+h7Cr17XwSN50='; img-src * data:; object-src 'none'; style-src 'self' 'sha256-PEo9RAB0C7Pw0EzsgS+HuVOHFPZRr93dZNJ9mYzz95A=' 'sha256-lrhGtO62QsLjBNsk1WGpvRZbHmXYMG/CmjhMFhLDJpg=' 'sha256-4SHcbKIX1Fx5rhlottWqqHggM5MSJz+xg9uwwq8UK74=' 'sha256-bQfd5MwpJ1ZxnVh373acxlqXQ8VwM88QhEHNuQ0ppNg=' 'sha256-yIJ6JcbosmUis6IDdnDV7NU2qPkU/1KUJHRwj44Q32o='; frame-src 'self'; connect-src 'self'; form-action 'self'; base-uri 'self';frame-ancestors 'self'; report-uri https://searx.report-uri.com/r/d/csp/enforce;";
add_header X-Content-Security-Policy "default-src 'none'; font-src 'self'; script-src 'self' 'sha256-2XPtiIriLCRC9BnusBjoTHsfh5zn7F+h7Cr17XwSN50='; img-src * data:; object-src 'none'; style-src 'self' 'sha256-PEo9RAB0C7Pw0EzsgS+HuVOHFPZRr93dZNJ9mYzz95A=' 'sha256-lrhGtO62QsLjBNsk1WGpvRZbHmXYMG/CmjhMFhLDJpg=' 'sha256-4SHcbKIX1Fx5rhlottWqqHggM5MSJz+xg9uwwq8UK74=' 'sha256-bQfd5MwpJ1ZxnVh373acxlqXQ8VwM88QhEHNuQ0ppNg=' 'sha256-yIJ6JcbosmUis6IDdnDV7NU2qPkU/1KUJHRwj44Q32o='; frame-src 'self'; connect-src 'self'; form-action 'self'; base-uri 'self';frame-ancestors 'self'; report-uri https://https://searx.report-uri.com/r/d/csp/enforce;";
add_header X-WebKit-CSP "default-src 'none'; font-src 'self'; script-src 'self' 'sha256-2XPtiIriLCRC9BnusBjoTHsfh5zn7F+h7Cr17XwSN50='; img-src * data:; object-src 'none'; style-src 'self' 'sha256-PEo9RAB0C7Pw0EzsgS+HuVOHFPZRr93dZNJ9mYzz95A=' 'sha256-lrhGtO62QsLjBNsk1WGpvRZbHmXYMG/CmjhMFhLDJpg=' 'sha256-4SHcbKIX1Fx5rhlottWqqHggM5MSJz+xg9uwwq8UK74=' 'sha256-bQfd5MwpJ1ZxnVh373acxlqXQ8VwM88QhEHNuQ0ppNg=' 'sha256-yIJ6JcbosmUis6IDdnDV7NU2qPkU/1KUJHRwj44Q32o='; frame-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self';frame-ancestors 'self'; report-uri https://searx.report-uri.com/r/d/csp/enforce;";
add_header Expect-CT "max-age=0; report-uri=https://searx.report-uri.com/r/d/ct/reportOnly;";
add_header Expect-Staple "max-age=0; report-uri=https://searx.report-uri.com/r/d/staple/reportOnly";
}
location /stats {
rewrite / https://stats.searx.xyz;
}
}
Die Angabe ssl_protocols TLSv1.2 TLSv1.3;
könnte man auch direkt in die nginx.conf
auslagern, sie ist, ähnlich wie die Aktivierung von SPDY global für jeden vHost mit derselben IP-Adresse.