diff --git a/group_vars/django_cas.yml b/group_vars/django_cas.yml
index ffed7acea02ae2ae5ae483c84d3946058588861d..a576a8c70d41dbc3d00f207aa458a03ea717964d 100644
--- a/group_vars/django_cas.yml
+++ b/group_vars/django_cas.yml
@@ -2,13 +2,6 @@
 glob_django_cas:
   repo: 'http://gitlab.adm.crans.org/nounous/django-cas.git'
   path: '/var/local/django-cas'
-  url:
-    - cas.crans.org
-    - cas.adm.crans.org
-    - login.crans.org
-    - login.adm.crans.org
-    - auth.crans.org
-    - auth.adm.crans.org
   ldap:
     dn: 'cn=Utilisateurs,dc=crans,dc=org'
     password: "{{ vault.cas_ldap_password }}"
@@ -18,6 +11,30 @@ glob_django_cas:
     host: tealc.adm.crans.org
     password: "{{ vault.cas_database_password }}"
   secret_key: "{{ vault.cas_secret_key }}"
-  reverse_proxy:
-    - '10.231.136.0/24'
-    - '2a0c:700:0:2::/64'
+
+loc_nginx:
+  service_name: "cas"
+  ssl: []
+  servers:
+    - server_name:
+        - cas.crans.org
+        - cas.adm.crans.org
+        - login.crans.org
+        - login.adm.crans.org
+        - auth.crans.org
+        - auth.adm.crans.org
+      default: true
+      locations:
+        - filter: "/cas"
+          params:
+            - "rewrite ^/cas$ / redirect"
+            - "rewrite ^/cas/(.*)$ /$1 redirect"
+
+        - filter: "/static"
+          params:
+            - "alias /var/local/django-cas/cas/local_static"
+
+        - filter: "/"
+          params:
+            - "uwsgi_pass unix:///var/run/uwsgi/app/cas/socket"
+            - "include uwsgi_params"
diff --git a/group_vars/mailman.yml b/group_vars/mailman.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fe7a0de7484a5cce7f0f355cf5f4c52d45b29b65
--- /dev/null
+++ b/group_vars/mailman.yml
@@ -0,0 +1,61 @@
+---
+loc_nginx:
+  service_name: mailman
+  default_server: lists.crans.org
+  default_ssl_server: lists.crans.org
+  auth_passwd:
+    Stop: "$apr1$NXaV5H7Q$J3ora3Jo5h775Y1nm93PN1"
+  deploy_robots_file: true
+  servers:
+    - server_name:
+      - lists.crans.org
+      ssl: crans.org
+      root: "/usr/lib/cgi-bin/mailman/"
+      index:
+        - index.htm
+        - index.html
+      locations:
+        - filter: "/error/"
+          params:
+            - "internal"
+            - "alias /var/www/html/"
+        - filter: "/create"
+          params:
+            - "default_type text/html"
+            - "alias /etc/mailman/create.html"
+        - filter: "~ ^/$"
+          params:
+            - "return 302 https://lists.crans.org/listinfo"
+        - filter: "/"
+          params:
+            - "include \"/etc/nginx/snippets/fastcgi-mailman.conf\""
+        - filter: "~ ^/listinfo"
+          params:
+            - "satisfy any"
+            - "include \"/etc/nginx/snippets/fastcgi-mailman.conf\""
+            - "allow 185.230.76.0/22"
+            - "allow 2a0c:700:0::/40"
+            - "deny all"
+            - "auth_basic \"On n'aime pas les spambots, donc on a mis un mot de passe. Le login est Stop et le mot de passe est Spam.\""
+            - "auth_basic_user_file /etc/nginx/passwd"
+            - "error_page 401 /error/401.html"
+        - filter: "~ ^/admin"
+          params:
+            - "satisfy any"
+            - "include \"/etc/nginx/snippets/fastcgi-mailman.conf\""
+            - "allow 185.230.76.0/22"
+            - "allow 2a0c:700:0::/40"
+            - "deny all"
+            - "auth_basic \"On n'aime pas les spambots, donc on a mis un mot de passe. Le login est Stop et le mot de passe est Spam.\""
+            - "auth_basic_user_file /etc/nginx/passwd"
+            - "error_page 401 /error/401.html"
+        - filter: "/images/mailman"
+          params:
+            - "alias /usr/share/images/mailman"
+        - filter: "/robots.txt"
+          params:
+            - "alias /var/www/robots.txt"
+        - filter: "/archives"
+          params:
+            - "alias /var/lib/mailman/archives/public"
+            - "autoindex on"
diff --git a/group_vars/nginx.yml b/group_vars/nginx.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e28685411e2d5cab3d65718b2e4f3e04e32c9ae1
--- /dev/null
+++ b/group_vars/nginx.yml
@@ -0,0 +1,32 @@
+---
+glob_nginx:
+  contact: contact@crans.org
+  who: "L'équipe technique du Cr@ns"
+  service_name: service
+  ssl:
+    # Add adm.crans.org if necessary
+    - name: crans.org
+      cert: /etc/letsencrypt/live/crans.org/fullchain.pem
+      cert_key: /etc/letsencrypt/live/crans.org/privkey.pem
+      trusted_cert: /etc/letsencrypt/live/crans.org/chain.pem
+  servers:
+    - ssl: false  # Replace by crans.org or adm.crans.org
+      default: true
+      server_name:
+        - "default"
+        - "_"
+      root: "/var/www/html"
+      locations:
+        - filter: "/"
+          params: []
+      additional_params: []
+  upstreams: []
+
+  auth_passwd: []
+  default_server:
+  default_ssl_server:
+  default_ssl_domain: crans.org
+  real_ip_from:
+    - "172.16.0.0/16"
+    - "fd00:0:0:10::/64"
+  deploy_robots_file: false
diff --git a/group_vars/nginx_rtmp.yml b/group_vars/nginx_rtmp.yml
deleted file mode 100644
index d5626daa0dca8487e3fa234c57ca8db1076db5f8..0000000000000000000000000000000000000000
--- a/group_vars/nginx_rtmp.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-
-glob_nginx_rtmp:
-  uri: stream.crans.org
diff --git a/group_vars/reverseproxy.yml b/group_vars/reverseproxy.yml
index 49f1ed78725192ce304bff263303f08c5f73e29b..3be4680eb6ef6a4526e71bb131077cbdf85356f3 100644
--- a/group_vars/reverseproxy.yml
+++ b/group_vars/reverseproxy.yml
@@ -1,18 +1,21 @@
-certbot:
-  dns_rfc2136_name: certbot_challenge.
-  dns_rfc2136_secret: "{{ vault.certbot_dns_secret }}"
-  mail: root@crans.org
-  certname: crans.org
-  domains: "crans.org, *.crans.org, crans.fr, *.crans.fr, crans.eu, *.crans.eu"
+loc_certbot:
+  - dns_rfc2136_server: '172.16.10.147'
+    dns_rfc2136_name: certbot_challenge.
+    dns_rfc2136_secret: "{{ vault.certbot_dns_secret }}"
+    mail: root@crans.org
+    certname: crans.org
+    domains: "crans.org, *.crans.org, crans.fr, *.crans.fr, crans.eu, *.crans.eu"
 
-nginx:
-  contact: contact@crans.org
-  who: "l'équipe technique du Cr@ns"
+loc_nginx:
+  servers: []
   ssl:
-    cert: /etc/letsencrypt/live/crans.org/fullchain.pem
-    cert_key: /etc/letsencrypt/live/crans.org/privkey.pem
-    trusted_cert: /etc/letsencrypt/live/crans.org/chain.pem
+    - name: crans.org
+      cert: /etc/letsencrypt/live/crans.org/fullchain.pem
+      cert_key: /etc/letsencrypt/live/crans.org/privkey.pem
+      trusted_cert: /etc/letsencrypt/live/crans.org/chain.pem
 
+
+glob_reverseproxy:
   redirect_dnames:
     - crans.eu
     - crans.fr
diff --git a/group_vars/roundcube.yml b/group_vars/roundcube.yml
index 9c32c7d035bcc16f1579e38d840e75781e21a255..7e6601705c8ecce4b2b36028dbaf94db759f4d66 100644
--- a/group_vars/roundcube.yml
+++ b/group_vars/roundcube.yml
@@ -1,4 +1,4 @@
-roundcube_glob:
+glob_roundcube:
   name: Crans
   imap_server: owl.adm.crans.org
   smtp_server: smtp.adm.crans.org
@@ -29,3 +29,24 @@ roundcube_glob:
     elastic: https://www.crans.org/images/crans.svg
     larry: https://www.crans.org/images/crans_banner.png
     classic: https://www.crans.org/images/crans_banner.png
+
+loc_nginx:
+  service_name: "roundcube"
+  ssl: []
+  servers:
+    - server_name: "{{ query('ldap', 'ip', ansible_hostname, 'adm') | ipwrap + [ansible_hostname, ansible_hostname + '.adm.crans.org'] }}"
+      default: true
+      root: "/var/lib/roundcube"
+      locations:
+        - filter: "~ \\.php$"
+          params:
+            - "include snippets/fastcgi-php.conf"
+            - "fastcgi_buffer_size 128k"
+            - "fastcgi_buffers 4 256k"
+            - "fastcgi_busy_buffers_size 256k"
+            - "fastcgi_pass unix:/var/run/php/php7.3-fpm.sock"
+            - "include fastcgi_params"
+      additional_params:
+        - "index index.php index.htm index.html"
+        - "try_files $uri $uri/ /index.php?q=$uri&$args"
+        - "client_max_body_size 10G"
diff --git a/group_vars/thelounge.yml b/group_vars/thelounge.yml
new file mode 100644
index 0000000000000000000000000000000000000000..66132cd122236c7a7f69b1588d7bbd1cadaa3bb9
--- /dev/null
+++ b/group_vars/thelounge.yml
@@ -0,0 +1,25 @@
+glob_thelounge:
+  public: "false"
+  host: "undefined"
+  reverseProxy: "false"
+  oidentd: "null"
+  irc:
+    name: Crans
+    host: irc.crans.org
+    port: 6697
+    password:
+    tls: "true"
+    rejectUnauthorized: "true"
+    nick: "thelounge%%"
+    username: "thelounge"
+    realname: "The Lounge User"
+    join: "#general"
+  ldap_enable: "false"
+  ldap:
+    url: "ldap://172.16.10.157"
+    primaryKey: "cn"
+    rootDN: "cn=thelounge,ou=service-users,dc=crans,dc=org"
+    rootPassword: "{{ vault.ldap_thelounge }}"
+    filter: "(objectclass=inetOrgPerson)"
+    base: "dc=crans,dc=org"
+    scope: "sub"
diff --git a/group_vars/wiki.yml b/group_vars/wiki.yml
new file mode 100644
index 0000000000000000000000000000000000000000..63b6a0fef8a6487c87fcf786ae73690eea50adfc
--- /dev/null
+++ b/group_vars/wiki.yml
@@ -0,0 +1,37 @@
+---
+glob_moinmoin:
+  main: false
+
+loc_nginx:
+  service_name: wiki
+  ssl: []
+  servers:
+    - server_name: "{{ query('ldap', 'ip', ansible_hostname, 'adm') | ipwrap + [ansible_hostname, ansible_hostname + '.adm.crans.org'] }}"
+      default: true
+      access_log: "/var/log/nginx/wiki.log combined"
+      error_log: "/var/log/nginx/wiki.error.log"
+      additional_params:
+        - "rewrite ^/$ $scheme://wiki.crans.org/PageAccueil"
+        - "client_max_body_size 15M"
+
+      locations:
+        - filter: "/wiki"
+          params:
+            - "alias /var/local/wiki/htdocs/"
+
+        - filter: "/robots.txt"
+          params:
+            - "alias /var/local/wiki/robots.txt"
+
+        - filter: "/favicon.ico"
+          params:
+            - "alias /var/local/wiki/favicon.ico"
+
+        - filter: "/www-sitemap.xml"
+          params:
+            - "alias /var/local/wiki/www-sitemap.xml"
+
+        - filter: "/"
+          params:
+            - "uwsgi_pass unix:///var/run/uwsgi/app/moinmoin/socket"
+            - "include uwsgi_params"
diff --git a/host_vars/charybde.adm.crans.org.yml b/host_vars/charybde.adm.crans.org.yml
index 5eea3c44be25d3438f9ca0834728350458570b7f..00db4ce78f74462bf34ef6222aa57cfa0abbdf35 100644
--- a/host_vars/charybde.adm.crans.org.yml
+++ b/host_vars/charybde.adm.crans.org.yml
@@ -33,3 +33,31 @@ to_backup:
   hosts_allow: "*",
   read_only: "yes"
   }
+
+loc_nginx:
+  service_name: ftp
+  ssl: []
+  servers:
+    - server_name:
+        - "ftp"
+        - "ftp.*"
+        - "mirror"
+        - "mirror.*"
+        - "archive.ubuntu.com"
+        - "fr.archive.ubuntu.com"
+        - "security.ubuntu.com"
+        - "ftps"
+        - "ftps.*"
+      root: "/pubftp"
+      locations:
+        - filter: "/"
+          params:
+          - "autoindex on"
+          - "autoindex_exact_size off"
+          - "add_before_body /.html/HEADER.html"
+          - "add_after_body /.html/FOOTER.html"
+        - filter: "/pub/events/"
+          params:
+            - "mp4"
+            - "mp4_buffer_size 1m"
+            - "mp4_max_buffer_size 5m"
diff --git a/host_vars/irc.adm.crans.org.yml b/host_vars/irc.adm.crans.org.yml
index 53d3a98a63a5a588ccdc109e84b1082b780f441b..a093bda0c29133822fefedd0470611661e746680 100644
--- a/host_vars/irc.adm.crans.org.yml
+++ b/host_vars/irc.adm.crans.org.yml
@@ -2,3 +2,34 @@
 interfaces:
   adm: ens18
   srv: ens19
+
+loc_certbot:
+  - dns_rfc2136_server: '172.16.10.147'
+    dns_rfc2136_name: certbot_challenge.
+    dns_rfc2136_secret: "{{ vault.certbot_dns_secret }}"
+    mail: root@crans.org
+    certname: crans.org
+    domains: "irc.crans.org"
+
+loc_nginx:
+  service_name: "thelounge"
+  servers:
+    - server_name:
+        - "irc.crans.org"
+        - "irc"
+      default: true
+      ssl: crans.org
+      locations:
+        - filter: "^~ /web/"
+          params:
+            - "proxy_pass http://localhost:9000/"
+            - "include \"/etc/nginx/snippets/options-proxypass.conf\""
+        - filter: "~ ^/$"
+          params:
+            - "return 302 https://irc.crans.org/web/"
+        - filter: "/"
+          params:
+            - "return 302 \"https://wiki.crans.org/VieCrans/UtiliserIrc#Via_l.27interface_web\""
+
+loc_thelounge:
+  public: "true"
diff --git a/host_vars/kiwi.adm.crans.org.yml b/host_vars/kiwi.adm.crans.org.yml
index 162f19448fa5423c41fe5ae04aa910bb07cd0c3f..5ed645966c4b1338786bb376e9a9df161e32da20 100644
--- a/host_vars/kiwi.adm.crans.org.yml
+++ b/host_vars/kiwi.adm.crans.org.yml
@@ -31,5 +31,5 @@ to_backup:
   read_only: "yes",
   }
 
-moinmoin:
+loc_moinmoin:
   main: true
diff --git a/host_vars/redisdead.adm.crans.org.yml b/host_vars/redisdead.adm.crans.org.yml
index 8228a1d0f68dc4a674859cef562009440f788244..f562ec360aba3bb7e26b649dd83ccf1312e22cf9 100644
--- a/host_vars/redisdead.adm.crans.org.yml
+++ b/host_vars/redisdead.adm.crans.org.yml
@@ -33,3 +33,11 @@ to_backup:
   secrets_file: "/etc/rsyncd.secrets",
   hosts_allow: ["zephir.adm.crans.org", "10.231.136.6"],
   }
+
+loc_certbot:
+  - dns_rfc2136_server: '172.16.10.147'
+    dns_rfc2136_name: certbot_challenge.
+    dns_rfc2136_secret: "{{ vault.certbot_dns_secret }}"
+    mail: root@crans.org
+    certname: crans.org
+    domains: "*.crans.org"
diff --git a/host_vars/sputnik.adm.crans.org b/host_vars/sputnik.adm.crans.org
deleted file mode 100644
index 2878a5780c0ce3ee4cdb26aea6b7cec14a69a7ec..0000000000000000000000000000000000000000
--- a/host_vars/sputnik.adm.crans.org
+++ /dev/null
@@ -1,5 +0,0 @@
----
-loc_slapd:
-  ip: "{{ query('ldap', 'ip', 'sputnik', 'adm') | ipv4 | first }}"
-  replica: true
-  replica_rid: 4
diff --git a/host_vars/sputnik.adm.crans.org.yml b/host_vars/sputnik.adm.crans.org.yml
index 6b2473f11c2035e2b83290955e8c50a82fe70d6c..7e6ff41c5707a678975880815c188a73962074d5 100644
--- a/host_vars/sputnik.adm.crans.org.yml
+++ b/host_vars/sputnik.adm.crans.org.yml
@@ -23,5 +23,77 @@ to_backup:
   hosts_allow: ["zephir.adm.crans.org", "10.231.136.6", "172.31.0.1"],
   }
 
-moinmoin:
+loc_slapd:
+  ip: "{{ query('ldap', 'ip', 'sputnik', 'adm') | ipv4 | first }}"
+  replica: true
+  replica_rid: 4
+
+loc_moinmoin:
   main: false
+
+loc_certbot:
+  - dns_rfc2136_server: '172.16.10.147'
+    dns_rfc2136_name: certbot_adm_challenge.
+    dns_rfc2136_secret: "{{ vault.certbot_adm_dns_secret }}"
+    mail: root@crans.org
+    certname: adm.crans.org
+    domains: "*.adm.crans.org"
+  - dns_rfc2136_server: '172.16.10.147'
+    dns_rfc2136_name: certbot_challenge.
+    dns_rfc2136_secret: "{{ vault.certbot_dns_secret }}"
+    mail: root@crans.org
+    certname: crans.org
+    domains: "*.crans.org"
+
+loc_nginx:
+  service_name: wiki
+  ssl:
+    - name: adm.crans.org
+      cert: /etc/letsencrypt/live/adm.crans.org/fullchain.pem
+      cert_key: /etc/letsencrypt/live/adm.crans.org/privkey.pem
+      trusted_cert: /etc/letsencrypt/live/adm.crans.org/chain.pem
+    - name: crans.org
+      cert: /etc/letsencrypt/live/crans.org/fullchain.pem
+      cert_key: /etc/letsencrypt/live/crans.org/privkey.pem
+      trusted_cert: /etc/letsencrypt/live/crans.org/chain.pem
+  servers:
+    - server_name:
+        - "wiki2.crans.org"
+      ssl : "crans.org"
+      access_log: "/var/log/nginx/wiki.log combined"
+      error_log: "/var/log/nginx/wiki.error.log"
+      additional_params:
+        - "rewrite ^/$ $scheme://wiki2.crans.org/PageAccueil"
+        - "client_max_body_size 15M"
+
+      locations:
+        - filter: "/wiki"
+          params:
+            - "alias /var/local/wiki/htdocs/"
+
+        - filter: "/robots.txt"
+          params:
+            - "alias /var/local/wiki/robots.txt"
+
+        - filter: "/favicon.ico"
+          params:
+            - "alias /var/local/wiki/favicon.ico"
+
+        - filter: "/www-sitemap.xml"
+          params:
+            - "alias /var/local/wiki/www-sitemap.xml"
+
+        - filter: "/"
+          params:
+            - "uwsgi_pass unix:///var/run/uwsgi/app/moinmoin/socket"
+            - "include uwsgi_params"
+
+loc_reverseproxy:
+  reverseproxy_sites:
+    - {from: status.crans.org, to: "127.0.0.1:8080"}
+    - {from: git2.crans.org, to: "127.0.0.1:3000"}
+    - {from: git2.adm.crans.org, to: "127.0.0.1:3000", ssl: adm.crans.org}
+
+  redirect_sites: []
+
+  static_sites: []
diff --git a/host_vars/zamok.adm.crans.org.yml b/host_vars/zamok.adm.crans.org.yml
index 4b02629e74b951fb0f2a5aca5cd172a8f753b5b0..8949c4df91fa343adc3d486f848efb7d90879760 100644
--- a/host_vars/zamok.adm.crans.org.yml
+++ b/host_vars/zamok.adm.crans.org.yml
@@ -8,3 +8,9 @@ loc_borg:
       params:
         - "- name: all"
         - "  password: {{ vault.mysql_zamok_password }}"
+
+loc_thelounge:
+  host: "\"172.16.10.31\""
+  oidentd: "\"/usr/local/lib/thelounge/.oidentd.conf\""
+  reverseProxy: "true"
+  ldap_enable: "true"
diff --git a/hosts b/hosts
index 397f791cd90650834e6c02ee837f3d0173c9c22a..e66ffb179767b1f300f40aac08966f43b9d02e30 100644
--- a/hosts
+++ b/hosts
@@ -20,11 +20,16 @@ tealc.adm.crans.org
 [belenios]
 belenios.adm.crans.org
 
+[certbot]
+sputnik.adm.crans.org
+
 [certbot:children]
 dovecot
 git
+mailman
 radius  # We use certbot to manage LE certificates
 reverseproxy
+thelounge
 
 [dhcp:children]
 routeurs_vm
@@ -77,17 +82,22 @@ sputnik.adm.crans.org
 [linx]
 linx.adm.crans.org
 
+[mailman]
+redisdead.adm.crans.org
+
 [monitoring]
 monitoring.adm.crans.org
 
 [nginx]
 charybde.adm.crans.org
 
-[nginx_rtmp]
-fluxx.adm.crans.org
-
 [nginx:children]
+django_cas
+mailman
 reverseproxy
+roundcube
+thelounge
+wiki
 
 [ntp_server]
 charybde.adm.crans.org
@@ -113,6 +123,7 @@ radius
 
 [reverseproxy]
 hodaur.adm.crans.org
+sputnik.adm.crans.org
 
 [roundcube]
 roundcube.adm.crans.org
@@ -122,11 +133,19 @@ routeur-daniel.adm.crans.org
 routeur-jack.adm.crans.org
 routeur-sam.adm.crans.org
 
+[thelounge]
+irc.adm.crans.org
+zamok.adm.crans.org
+
 [virtu]
 daniel.adm.crans.org
 jack.adm.crans.org
 sam.adm.crans.org
 
+[wiki]
+kiwi.adm.crans.org
+sputnik.adm.crans.org
+
 [crans_routeurs:children]
 # dhcp  TODO: Really needed ?
 # keepalived
diff --git a/plays/cas.yml b/plays/cas.yml
index a55a8ab1140a3d6c4c45c4104cc44170dc8f997a..634f03e4f4b2284fb6c1ea46823941e7845ef5c4 100755
--- a/plays/cas.yml
+++ b/plays/cas.yml
@@ -5,5 +5,7 @@
 - hosts: django_cas
   vars:
     django_cas: "{{ glob_django_cas | default({}) | combine(loc_django_cas | default({})) }}"
+    nginx: "{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}"
   roles:
     - django-cas
+    - nginx
diff --git a/plays/irc.yml b/plays/irc.yml
new file mode 100755
index 0000000000000000000000000000000000000000..348f9d26c662c9ec15ec11aef9da114b9bd5f652
--- /dev/null
+++ b/plays/irc.yml
@@ -0,0 +1,15 @@
+#!/usr/bin/env ansible-playbook
+---
+- hosts: thelounge
+  vars:
+    thelounge: '{{ glob_thelounge | default({}) | combine(loc_thelounge | default({})) }}'
+  roles:
+    - thelounge
+
+- hosts: thelounge,!adh_server
+  vars:
+    certbot: '{{ loc_certbot | default(glob_certbot | default([])) }}'
+    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
+  roles:
+    - certbot
+    - nginx
diff --git a/plays/mailman.yml b/plays/mailman.yml
index 6a84058baaa48fb0f27dd1c613ac9f1bc165172b..ac7afd009a7cb6b418ec1aa45473d204f2d99870 100755
--- a/plays/mailman.yml
+++ b/plays/mailman.yml
@@ -8,8 +8,6 @@
       default_url: "https://lists.crans.org/"
       default_host: "lists.crans.org"
       default_language: "fr"
-      auth_basic: |
-        "On n'aime pas les spambots, donc on a mis un mot de passe. Le login est Stop et le mot de passe est Spam.";
       custom_logo: "crans_icon_dark.svg"
       custom_logo_name: "crans.svg"
       custom_logo_url: "https://www.crans.org/"
@@ -17,14 +15,10 @@
     spamassassin: "SpamAssassin_crans"
     smtphost: "smtp.adm.crans.org"
     mynetworks: ['138.231.0.0/16', '185.230.76.0/22', '2a0c:700:0::/40']
-    nginx:
-      ssl:
-        cert: /etc/letsencrypt/live/crans.org/fullchain.pem
-        key: /etc/letsencrypt/live/crans.org/privkey.pem
-        trusted_cert: /etc/letsencrypt/live/crans.org/chain.pem
+    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
   roles:
     - mailman
-    - nginx-mailman
+    - nginx
 
 # Deploy Mailman3
 - hosts: mailman.adm.crans.org
diff --git a/plays/mirror.yml b/plays/mirror.yml
index d776c8c8abbe38f809f450db3d3965412f2662b9..56f83b3c4c861472acd707e1c005a4a23d55b125 100755
--- a/plays/mirror.yml
+++ b/plays/mirror.yml
@@ -71,7 +71,9 @@
         cron_time: "00 5"
         rsync_host: cdimage.ubuntu.com
         rsync_path: cdimage/ubuntu-mate/releases
+
+    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
   roles:
     - ftpsync
     - rsync-mirror
-    - nginx-pubftp
+    - nginx
diff --git a/plays/moinmoin.yml b/plays/moinmoin.yml
index b9c63047eda6f6184692dcb947ec76d51fecead0..35207855f2aa8048a14445b27a68c26e3d595130 100755
--- a/plays/moinmoin.yml
+++ b/plays/moinmoin.yml
@@ -1,6 +1,16 @@
 #!/usr/bin/env ansible-playbook
 ---
+- hosts: certbot:&wiki
+  vars:
+    certbot: '{{ loc_certbot | default(glob_certbot | default([])) }}'
+  roles:
+    - certbot
+
 # Deploy MoinMoin Wiki
-- hosts: kiwi.adm.crans.org,soyouz.adm.crans.org,sputnik.adm.crans.org
+- hosts: wiki
+  vars:
+    moinmoin: '{{ glob_moinmoin | default({}) | combine(loc_moinmoin | default({})) }}'
+    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
   roles:
     - moinmoin
+    - nginx
diff --git a/plays/nginx.yml b/plays/nginx.yml
new file mode 100755
index 0000000000000000000000000000000000000000..7cb58317f665513bc97bfdb553b81323f5edc2b8
--- /dev/null
+++ b/plays/nginx.yml
@@ -0,0 +1,8 @@
+#!/usr/bin/env ansible-playbook
+---
+# Deploy Nginx
+- hosts: nginx,!adh_server
+  vars:
+    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
+  roles:
+    - nginx
diff --git a/plays/nginx_rtmp.yml b/plays/nginx_rtmp.yml
deleted file mode 100755
index b515bb231fb5122a3b8779d5ddfaa2923be40087..0000000000000000000000000000000000000000
--- a/plays/nginx_rtmp.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env ansible-playbook
----
-- hosts: nginx_rtmp
-  vars:
-    nginx_rtmp: "{{ glob_nginx_rtmp | default({}) | combine(loc_nginx_rtmp | default({})) }}"
-  roles:
-    - nginx-rtmp
diff --git a/plays/reverse-proxy.yml b/plays/reverse-proxy.yml
index 04c3fb38f94672e9d346b8b6b9cc621f3a5948b0..3b03f0a9f2cb56b963aad38f15d51d55a6504e30 100755
--- a/plays/reverse-proxy.yml
+++ b/plays/reverse-proxy.yml
@@ -3,7 +3,8 @@
 - hosts: reverseproxy
   vars:
     certbot: '{{ loc_certbot | default(glob_certbot | default([])) }}'
-    mirror: '{{ glob_mirror.name }}'
+    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
+    reverseproxy: '{{ glob_reverseproxy | default({}) | combine(loc_reverseproxy | default({})) }}'
   roles:
     - certbot
-    - nginx-reverseproxy
+    - nginx
diff --git a/plays/roundcube.yml b/plays/roundcube.yml
index 996ca7c430a13d790d97e88328c9db124bf35057..c57e892026bde30e596178384c3081772e977d47 100755
--- a/plays/roundcube.yml
+++ b/plays/roundcube.yml
@@ -3,6 +3,8 @@
 
 - hosts: roundcube
   vars:
-    roundcube: '{{ roundcube_glob | default({}) | combine(roundcube_loc | default({})) }}'
+    nginx: '{{ glob_nginx | default({}) | combine(loc_nginx | default({})) }}'
+    roundcube: '{{ glob_roundcube | default({}) | combine(loc_roundcube | default({})) }}'
   roles:
     - roundcube
+    - nginx
diff --git a/roles/certbot/tasks/main.yml b/roles/certbot/tasks/main.yml
index 812aff2cb551c659f376e78f4ead3142b1a7269e..91e2fde88b64b661c3255bda72f48fa51583b93e 100644
--- a/roles/certbot/tasks/main.yml
+++ b/roles/certbot/tasks/main.yml
@@ -29,7 +29,7 @@
   template:
     src: "letsencrypt/dhparam.j2"
     dest: "/etc/letsencrypt/dhparam"
-    mode: 0644
+    mode: 0600
 
 - name: Create /etc/letsencrypt/conf.d
   file:
@@ -46,3 +46,12 @@
 - name: Run certbot
   command: certbot --non-interactive --config /etc/letsencrypt/conf.d/{{ item.certname }}.ini certonly
   loop: "{{ certbot }}"
+
+- name: Clean old files
+  file:
+    path: "{{ item }}"
+    state: absent
+  loop:
+    - "/etc/letsencrypt/options-ssl-nginx.conf"
+    - "/etc/letsencrypt/ssl-dhparams.pem"
+    - "/etc/letsencrypt/rfc2136.ini"
diff --git a/roles/django-cas/handlers/main.yml b/roles/django-cas/handlers/main.yml
index fe8fbf15fc08e4d27d8e6c85b561342d0749efa5..ba46876d693c3d932eae4d3350bb74d9a0651d86 100644
--- a/roles/django-cas/handlers/main.yml
+++ b/roles/django-cas/handlers/main.yml
@@ -1,9 +1,4 @@
 ---
-- name: Restart nginx
-  service:
-    name: nginx
-    state: restarted
-
 - name: Restart uwsgi
   service:
     name: uwsgi
diff --git a/roles/django-cas/tasks/main.yml b/roles/django-cas/tasks/main.yml
index 3b40472cd43f06852cd9ff4eefb6a3a55230d011..cc854db143bc0a0a320f83d3a70bc64929d18b09 100644
--- a/roles/django-cas/tasks/main.yml
+++ b/roles/django-cas/tasks/main.yml
@@ -3,7 +3,6 @@
   apt:
     update_cache: true
     name:
-      - nginx
       - uwsgi
       - uwsgi-plugin-python3
       - python3-django
@@ -30,20 +29,6 @@
     owner: www-data
   notify: Restart uwsgi
 
-- name: Configure NGINX site
-  template:
-    src: nginx/sites-available/cas.j2
-    dest: /etc/nginx/sites-available/cas
-    mode: 0644
-  notify: Restart nginx
-
-- name: Enable nginx site
-  file:
-    src: /etc/nginx/sites-available/cas
-    dest: /etc/nginx/sites-enabled/cas
-    state: link
-  notify: Restart nginx
-
 - name: Configure UWSGI app
   template:
     src: uwsgi/apps-available/cas.ini.j2
diff --git a/roles/django-cas/templates/nginx/sites-available/cas.j2 b/roles/django-cas/templates/nginx/sites-available/cas.j2
deleted file mode 100644
index 2372ae9260c51c3f34d2443c637371a211eb5530..0000000000000000000000000000000000000000
--- a/roles/django-cas/templates/nginx/sites-available/cas.j2
+++ /dev/null
@@ -1,26 +0,0 @@
-{{ ansible_header | comment }}
-
-server {
-     server_name {{ django_cas.url | join(' ') }};
-     listen 80;
-     listen [::]:80;
-
-     location /cas {
-         rewrite ^/cas$ / redirect;
-         rewrite ^/cas/(.*)$ /$1 redirect;
-     }
-
-     location /static {
-         alias {{ django_cas.path }}/cas/local_static;
-     }
-
-{% for ip in django_cas.reverse_proxy | default([]) %}
-     set_real_ip_from {{ ip }};
-{% endfor %}
-     real_ip_header P-Real-Ip;
-
-     location / {
-         uwsgi_pass unix:///var/run/uwsgi/app/cas/socket;
-         include uwsgi_params;
-     }
-}
diff --git a/roles/ftpsync/tasks/main.yml b/roles/ftpsync/tasks/main.yml
index d5ff244af43585bf73367ba62f01e5284f7deab3..2e6ca8b9bb28a20b71f15604f4213d89f6ac9c89 100644
--- a/roles/ftpsync/tasks/main.yml
+++ b/roles/ftpsync/tasks/main.yml
@@ -32,3 +32,16 @@
     src: update-motd.d/05-service.j2
     dest: /etc/update-motd.d/05-ftpsync
     mode: 0755
+
+- name: Copy configuration files
+  template:
+    src: "{{ item.src }}"
+    dest: "{{ item.dest }}"
+    mode: 0644
+  loop:
+    - src: html/HEADER.html.j2
+      dest: /pubftp/.html/HEADER.html
+    - src: html/FOOTER.html.j2
+      dest: /pubftp/.html/FOOTER.html
+    - src: html/style.min.css.j2
+      dest: /pubftp/.html/style.min.css
diff --git a/roles/nginx-pubftp/templates/html/FOOTER.html.j2 b/roles/ftpsync/templates/html/FOOTER.html.j2
similarity index 100%
rename from roles/nginx-pubftp/templates/html/FOOTER.html.j2
rename to roles/ftpsync/templates/html/FOOTER.html.j2
diff --git a/roles/nginx-pubftp/templates/html/HEADER.html.j2 b/roles/ftpsync/templates/html/HEADER.html.j2
similarity index 100%
rename from roles/nginx-pubftp/templates/html/HEADER.html.j2
rename to roles/ftpsync/templates/html/HEADER.html.j2
diff --git a/roles/nginx-pubftp/templates/html/style.min.css.j2 b/roles/ftpsync/templates/html/style.min.css.j2
similarity index 100%
rename from roles/nginx-pubftp/templates/html/style.min.css.j2
rename to roles/ftpsync/templates/html/style.min.css.j2
diff --git a/roles/mailman/tasks/main.yml b/roles/mailman/tasks/main.yml
index 467ef9f0a29c58cec12e8acabcdbaa148c4a74b3..9a74a41ec8e2e71289965dd8d77ec02628b7b3a7 100644
--- a/roles/mailman/tasks/main.yml
+++ b/roles/mailman/tasks/main.yml
@@ -19,6 +19,14 @@
     - create.html
   notify: Reload mailman
 
+- name: Deploy mailman snippet
+  template:
+    src: "nginx/snippets/fastcgi-mailman.conf.j2"
+    dest: "/etc/nginx/snippets/fastcgi-mailman.conf"
+    owner: root
+    group: root
+    mode: 0644
+
 # Fanciness
 - name: Deploy custom logo
   copy:
diff --git a/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2 b/roles/mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2
similarity index 100%
rename from roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2
rename to roles/mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2
diff --git a/roles/moinmoin/handlers/main.yml b/roles/moinmoin/handlers/main.yml
index ea116cb886562e67848a82f6512d06191fdbf9ca..ba46876d693c3d932eae4d3350bb74d9a0651d86 100644
--- a/roles/moinmoin/handlers/main.yml
+++ b/roles/moinmoin/handlers/main.yml
@@ -3,8 +3,3 @@
   service:
     name: uwsgi
     state: restarted
-
-- name: Restart nginx
-  service:
-    name: nginx
-    state: restarted
diff --git a/roles/moinmoin/tasks/main.yml b/roles/moinmoin/tasks/main.yml
index 50049b0338bb0d5a8bd69af0660d3c80d788f24e..bef5dc5155c020e9f3de423e40c25a0719f17670 100644
--- a/roles/moinmoin/tasks/main.yml
+++ b/roles/moinmoin/tasks/main.yml
@@ -40,19 +40,6 @@
     enabled: true
     state: started
 
-- name: Configure nginx
-  template:
-    src: nginx/sites-available/wiki.j2
-    dest: /etc/nginx/sites-available/wiki
-  notify: Restart nginx
-
-- name: Activate nginx site
-  file:
-    src: /etc/nginx/sites-available/wiki
-    dest: /etc/nginx/sites-enabled/wiki
-    state: link
-  notify: Restart nginx
-
 - name: Indicate role in motd
   template:
     src: update-motd.d/05-service.j2
diff --git a/roles/moinmoin/templates/nginx/sites-available/wiki.j2 b/roles/moinmoin/templates/nginx/sites-available/wiki.j2
deleted file mode 100644
index 4c7482f094d71b7dbb05793526257b12007fe60a..0000000000000000000000000000000000000000
--- a/roles/moinmoin/templates/nginx/sites-available/wiki.j2
+++ /dev/null
@@ -1,31 +0,0 @@
-{{ ansible_header | comment }}
-
-server {
-    listen 80;
-    listen [::]:80;
-    server_name wiki.adm.crans.org;
-
-    access_log /var/log/nginx/wiki.log combined;
-    error_log /var/log/nginx/wiki.error.log;
-
-    # Redirect to home page
-    rewrite ^/$ $scheme://wiki.crans.org/PageAccueil;
-
-    # Limit uploads
-    client_max_body_size 15M;
-
-    # MoinMoin paths
-    location /wiki/ { alias /var/local/wiki/htdocs/; }
-    location /robots.txt { alias /var/local/wiki/robots.txt; }
-    location /favicon.ico { alias /var/local/wiki/favicon.ico; }
-    location /www-sitemap.xml { alias /var/local/wiki/www-sitemap.xml; }
-
-    location / {
-        uwsgi_pass unix:///var/run/uwsgi/app/moinmoin/socket;
-        include uwsgi_params;
-    }
-
-    set_real_ip_from 172.16.10.0/24;
-    set_real_ip_from fd00:0:0:10::/64;
-    real_ip_header X-Real-Ip;
-}
diff --git a/roles/nginx-mailman/tasks/main.yml b/roles/nginx-mailman/tasks/main.yml
deleted file mode 100644
index e2036b6bc2963f225db9f0d17c5df6c4300e2866..0000000000000000000000000000000000000000
--- a/roles/nginx-mailman/tasks/main.yml
+++ /dev/null
@@ -1,43 +0,0 @@
----
-- name: Install NGINX
-  apt:
-    update_cache: true
-    name:
-      - nginx
-  register: apt_result
-  retries: 3
-  until: apt_result is succeeded
-
-- name: Copy configuration files
-  template:
-    src: "{{ item.src }}"
-    dest: "{{ item.dest }}"
-  loop:
-    - src: nginx/sites-available/mailman.j2
-      dest: /etc/nginx/sites-available/mailman
-    - src: nginx/mailman_passwd.j2
-      dest: /etc/nginx/mailman_passwd
-    - src: nginx/snippets/fastcgi-mailman.conf.j2
-      dest: /etc/nginx/snippets/fastcgi-mailman.conf
-    - src: nginx/snippets/options-ssl.conf.j2
-      dest: /etc/nginx/snippets/options-ssl.conf
-    - src: var/www/robots.txt.j2
-      dest: /var/www/robots.txt
-    - src: var/www/custom_401.html.j2
-      dest: /var/www/custom_401.html
-  notify: Reload nginx
-
-- name: Enable mailman
-  file:
-    src: /etc/nginx/sites-available/mailman
-    dest: /etc/nginx/sites-enabled/mailman
-    state: link
-    force: true
-  when: not ansible_check_mode
-  notify: Reload nginx
-
-- name: Indicate role in motd
-  template:
-    src: update-motd.d/05-service.j2
-    dest: /etc/update-motd.d/05-nginx-mailman
-    mode: 0755
diff --git a/roles/nginx-mailman/templates/nginx/mailman_passwd.j2 b/roles/nginx-mailman/templates/nginx/mailman_passwd.j2
deleted file mode 100644
index 741d52d9ac75dc30444669b04b6c1ffa578d285d..0000000000000000000000000000000000000000
--- a/roles/nginx-mailman/templates/nginx/mailman_passwd.j2
+++ /dev/null
@@ -1,2 +0,0 @@
-{{ ansible_header | comment }}
-Stop:$apr1$NXaV5H7Q$J3ora3Jo5h775Y1nm93PN1
diff --git a/roles/nginx-mailman/templates/nginx/sites-available/mailman.j2 b/roles/nginx-mailman/templates/nginx/sites-available/mailman.j2
deleted file mode 100644
index ba13c111299886be2d83d738778ed799d4812293..0000000000000000000000000000000000000000
--- a/roles/nginx-mailman/templates/nginx/sites-available/mailman.j2
+++ /dev/null
@@ -1,94 +0,0 @@
-{{ ansible_header | comment }}
-server {
-	listen 80 default;
-	listen [::]:80 default;
-
-	server_name _;
-
-	location / {
-	    return 302 https://{{ mailman.default_host }}$request_uri;
-	}
-}
-
-# Redirect everybody to mailing lists
-server {
-	listen 443 default_server ssl;
-	listen [::]:443 default_server ssl;
-	server_name _;
-
-	include "/etc/nginx/snippets/options-ssl.conf";
-
-	location / {
-		 return 302 https://{{ mailman.default_host }}$request_uri;
-	}
-}
-
-server {
-	listen 443 ssl http2;
-	listen [::]:443 ssl http2;
-	server_name {{ mailman.default_host }};
-
-	include "/etc/nginx/snippets/options-ssl.conf";
-
-	root /usr/lib/cgi-bin/mailman/;
-	index index.htm index.html;
-
-        location /error/ {
-		internal;
-		alias /var/www/;
-        }
-
-	location /create {
-		default_type text/html;
-		alias /etc/mailman/create.html;
-	}
-
-	location ~ ^/$ {
-		return 302 https://{{ mailman.default_host }}/listinfo;
-	}
-
-	location / {
-		include "/etc/nginx/snippets/fastcgi-mailman.conf";
-	}
-
-        location ~ ^/listinfo {
-                satisfy any;
-		include "/etc/nginx/snippets/fastcgi-mailman.conf";
-
-		{% for net in mynetworks -%}
-                allow {{ net }};
-		{% endfor -%}
-                deny all;
-
-	        auth_basic {{ mailman.auth_basic }}
-		auth_basic_user_file /etc/nginx/mailman_passwd;
-
-		error_page 401 /error/custom_401.html;
-        }
-
-        location ~ ^/admin {
-                satisfy any;
-
-		include "/etc/nginx/snippets/fastcgi-mailman.conf";
-
-		{% for net in mynetworks -%}
-                allow {{ net }};
-		{% endfor -%}
-                deny all;
-
-	        auth_basic {{ mailman.auth_basic }}
-		auth_basic_user_file /etc/nginx/mailman_passwd;
-		error_page 401 /error/custom_401.html;
-        }
-
-
-	location /images/mailman { alias /usr/share/images/mailman;}
-
-	location /robots.txt { alias /var/www/robots.txt;}
-
-	location /archives {
-		alias /var/lib/mailman/archives/public;
-		autoindex on;
-	}
-
-}
diff --git a/roles/nginx-mailman/templates/nginx/snippets/options-ssl.conf.j2 b/roles/nginx-mailman/templates/nginx/snippets/options-ssl.conf.j2
deleted file mode 100644
index 79d75395b48d40803b71faaec467eebe837c9467..0000000000000000000000000000000000000000
--- a/roles/nginx-mailman/templates/nginx/snippets/options-ssl.conf.j2
+++ /dev/null
@@ -1,17 +0,0 @@
-{{ ansible_header | comment }}
-
-ssl_certificate {{ nginx.ssl.cert }};
-ssl_certificate_key {{ nginx.ssl.key }};
-ssl_session_timeout 1d;
-ssl_session_cache shared:MozSSL:10m;
-ssl_session_tickets off;
-ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
-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 off;
-
-# Enable OCSP Stapling, point to certificate chain
-ssl_stapling on;
-ssl_stapling_verify on;
-ssl_trusted_certificate {{ nginx.ssl.trusted_cert }};
diff --git a/roles/nginx-mailman/templates/var/www/robots.txt.j2 b/roles/nginx-mailman/templates/var/www/robots.txt.j2
deleted file mode 100644
index 3fbaed7487cfaf8c21fbfe2e9ca63b89114d7eed..0000000000000000000000000000000000000000
--- a/roles/nginx-mailman/templates/var/www/robots.txt.j2
+++ /dev/null
@@ -1,4 +0,0 @@
-{{ ansible_header | comment }}
-
-User-agent: *
-Disallow: /
diff --git a/roles/nginx-pubftp/tasks/main.yml b/roles/nginx-pubftp/tasks/main.yml
deleted file mode 100644
index 2d5ccd7ae77e0d1b55f7e421437c9a7b9820873b..0000000000000000000000000000000000000000
--- a/roles/nginx-pubftp/tasks/main.yml
+++ /dev/null
@@ -1,28 +0,0 @@
----
-- name: Install NGINX
-  apt:
-    update_cache: true
-    name: nginx
-  register: apt_result
-  retries: 3
-  until: apt_result is succeeded
-
-- name: Copy configuration files
-  template:
-    src: "{{ item.src }}"
-    dest: "{{ item.dest }}"
-  loop:
-    - src: nginx/ftp.j2
-      dest: /etc/nginx/sites-available/ftp
-    - src: html/HEADER.html.j2
-      dest: /pubftp/.html/HEADER.html
-    - src: html/FOOTER.html.j2
-      dest: /pubftp/.html/FOOTER.html
-    - src: html/style.min.css.j2
-      dest: /pubftp/.html/style.min.css
-
-- name: Indicate role in motd
-  template:
-    src: update-motd.d/05-service.j2
-    dest: /etc/update-motd.d/05-nginx-pubftp
-    mode: 0755
diff --git a/roles/nginx-pubftp/templates/nginx/ftp.j2 b/roles/nginx-pubftp/templates/nginx/ftp.j2
deleted file mode 100644
index 7ebf0cf27a9c49798f103e121bb6146354f80078..0000000000000000000000000000000000000000
--- a/roles/nginx-pubftp/templates/nginx/ftp.j2
+++ /dev/null
@@ -1,22 +0,0 @@
-{{ ansible_header | comment }}
-server { 
-    listen 80;
-    listen [::]:80;
-    server_name ftp ftp.* mirror mirror.* archive.ubuntu.com fr.archive.ubuntu.com security.ubuntu.com ftps ftps.*;
-
-    root /pubftp;
-    index index.html;
-
-    location  /  {
-        autoindex on;
-        autoindex_exact_size off;
-        add_before_body /.html/HEADER.html;
-        add_after_body /.html/FOOTER.html;
-    }
-
-    location /pub/events/ {
-        mp4;
-        mp4_buffer_size     1m;
-        mp4_max_buffer_size 5m;
-    }
-}
diff --git a/roles/nginx-pubftp/templates/update-motd.d/05-service.j2 b/roles/nginx-pubftp/templates/update-motd.d/05-service.j2
deleted file mode 100755
index 82373d0b38e8376f20e82c033c12241c94018582..0000000000000000000000000000000000000000
--- a/roles/nginx-pubftp/templates/update-motd.d/05-service.j2
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/tail +14
-{{ ansible_header | comment }}
-> NGINX a été déployé sur cette machine. Voir /etc/nginx/.
diff --git a/roles/nginx-reverseproxy/handlers/main.yml b/roles/nginx-reverseproxy/handlers/main.yml
deleted file mode 100644
index 6dfcdd76195a9df2f3b033675c15311a32aeb581..0000000000000000000000000000000000000000
--- a/roles/nginx-reverseproxy/handlers/main.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Reload nginx
-  systemd:
-    name: nginx
-    state: reloaded
diff --git a/roles/nginx-reverseproxy/tasks/main.yml b/roles/nginx-reverseproxy/tasks/main.yml
deleted file mode 100644
index 5a23f9925baa8521d7885cc91ae54c75ac78ffad..0000000000000000000000000000000000000000
--- a/roles/nginx-reverseproxy/tasks/main.yml
+++ /dev/null
@@ -1,54 +0,0 @@
----
-- name: Install NGINX
-  apt:
-    update_cache: true
-    name: nginx
-  register: apt_result
-  retries: 3
-  until: apt_result is succeeded
-
-- name: Copy snippets
-  template:
-    src: "nginx/snippets/{{ item }}.j2"
-    dest: "/etc/nginx/snippets/{{ item }}"
-  loop:
-    - options-ssl.conf
-    - options-proxypass.conf
-
-- name: Copy dhparam
-  template:
-    src: letsencrypt/dhparam.j2
-    dest: /etc/letsencrypt/dhparam
-
-- name: Copy reverse proxy sites
-  template:
-    src: "nginx/sites-available/{{ item }}.j2"
-    dest: "/etc/nginx/sites-available/{{ item }}"
-  loop:
-    - reverseproxy
-    - reverseproxy_redirect_dname
-    - redirect
-  notify: Reload nginx
-
-- name: Activate sites
-  file:
-    src: "/etc/nginx/sites-available/{{ item }}"
-    dest: "/etc/nginx/sites-enabled/{{ item }}"
-    state: link
-  loop:
-    - reverseproxy
-    - reverseproxy_redirect_dname
-    - redirect
-  notify: Reload nginx
-  ignore_errors: "{{ ansible_check_mode }}"
-
-- name: Copy 50x error page
-  template:
-    src: www/html/50x.html.j2
-    dest: /var/www/html/50x.html
-
-- name: Indicate role in motd
-  template:
-    src: update-motd.d/05-service.j2
-    dest: /etc/update-motd.d/05-nginx
-    mode: 0755
diff --git a/roles/nginx-reverseproxy/templates/update-motd.d/05-service.j2 b/roles/nginx-reverseproxy/templates/update-motd.d/05-service.j2
deleted file mode 100755
index 82373d0b38e8376f20e82c033c12241c94018582..0000000000000000000000000000000000000000
--- a/roles/nginx-reverseproxy/templates/update-motd.d/05-service.j2
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/tail +14
-{{ ansible_header | comment }}
-> NGINX a été déployé sur cette machine. Voir /etc/nginx/.
diff --git a/roles/nginx-rtmp/files/index.html b/roles/nginx-rtmp/files/index.html
deleted file mode 100644
index 24c9c74943f877f1e64aec9739c5cad5a2b8a5e0..0000000000000000000000000000000000000000
--- a/roles/nginx-rtmp/files/index.html
+++ /dev/null
@@ -1,166 +0,0 @@
-<!DOCTYPE html>
-<html lang="fr">
-<head>
-  <meta charset="UTF-8">
-  <title>Crans Stream</title>
-  <link href="lib/bootstrap4/css/bootstrap.min.css" rel="stylesheet">
-  <link href="//unpkg.com/video.js@7/dist/video-js.min.css" rel="stylesheet">
-  <style>
-  body, html {
-    height: 100%;
-  }
-  .video-js .vjs-big-play-button {
-    display: none;
-  }
-  </style>
-</head>
-<body class="bg-dark">
-  <div class="container-fluid h-100 p-0">
-      <div class="row align-items-center h-100 mx-0">
-        <div class="col-lg-8">
-          <video id="my-video" class="video-js embed-responsive shadow-lg rounded-sm"></video>
-          <p class="text-right text-white">
-            <a class="text-white text-decoration-none" href="#" id="refreshStream">
-              <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrow-repeat" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-                <path fill-rule="evenodd" d="M2.854 7.146a.5.5 0 0 0-.708 0l-2 2a.5.5 0 1 0 .708.708L2.5 8.207l1.646 1.647a.5.5 0 0 0 .708-.708l-2-2zm13-1a.5.5 0 0 0-.708 0L13.5 7.793l-1.646-1.647a.5.5 0 0 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0 0-.708z"/>
-                <path fill-rule="evenodd" d="M8 3a4.995 4.995 0 0 0-4.192 2.273.5.5 0 0 1-.837-.546A6 6 0 0 1 14 8a.5.5 0 0 1-1.001 0 5 5 0 0 0-5-5zM2.5 7.5A.5.5 0 0 1 3 8a5 5 0 0 0 9.192 2.727.5.5 0 1 1 .837.546A6 6 0 0 1 2 8a.5.5 0 0 1 .501-.5z"/>
-              </svg>
-              Rafraîchir
-            </a>
-            —
-            <a class="text-white text-decoration-none" href="#" id="linkStream">
-              <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-box-arrow-up-right" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-                <path fill-rule="evenodd" d="M1.5 13A1.5 1.5 0 0 0 3 14.5h8a1.5 1.5 0 0 0 1.5-1.5V9a.5.5 0 0 0-1 0v4a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 0 0-1H3A1.5 1.5 0 0 0 1.5 5v8zm7-11a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-1 0V2.5H9a.5.5 0 0 1-.5-.5z"/>
-                <path fill-rule="evenodd" d="M14.354 1.646a.5.5 0 0 1 0 .708l-8 8a.5.5 0 0 1-.708-.708l8-8a.5.5 0 0 1 .708 0z"/>
-              </svg>
-              Ouvrir le flux dans une application externe
-            </a>
-            —
-            <a class="text-white text-decoration-none" href="#aboutModal" data-toggle="modal">
-              <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-info-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
-                <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
-                <path d="M8.93 6.588l-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588z"/>
-                <circle cx="8" cy="4.5" r="1"/>
-              </svg>
-              À propos
-            </a>
-          </p>
-        </div>
-        <div class="col-lg-4 h-100 px-0">
-          <div class="embed-responsive h-100 shadow-lg rounded-left">
-            <iframe src="https://irc.crans.org/web/?join=video_NOM&nick=viewer1&password=&realname=Viewer1"></iframe>
-          </div>
-        </div>
-      </div>
-  </div>
-
-  <div class="modal fade" id="aboutModal" tabindex="-1">
-    <div class="modal-dialog-centered modal-dialog modal-lg">
-      <div class="modal-content">
-        <div class="modal-header">
-          <h4 class="modal-title" id="exampleModalLabel">À propos</h4>
-          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-            <span aria-hidden="true">&times;</span>
-          </button>
-        </div>
-        <div class="modal-body">
-          <p>Crans Stream est un service maintenu par le <a href="https://crans.org/">Crans</a> permettant de diffuser un contenu vidéo. Il a pour but d'être utilisé pour diffuser des séminaires ou évènements.</p>
-
-          <h4>Comment je diffuse ?</h4>
-          <p>Pour diffuser un contenu vous devez être adhérent Crans et être sur le campus de Cachan.</p>
-          <h5>Avec Open Broadcaster Software</h5>
-          <p>
-            <a href="https://obsproject.com/">Open Broadcaster Software (OBS)</a>
-            est une solution libre et open source de diffusion vidéo.
-            Pour diffuser sur cette plateforme, allez dans l'onglet « Stream (flux) » des paramètres :
-          </p> 
-          <ul>
-            <li><b>Serveur :</b> <code>rtmps://stream.adm.crans.org:1935/live</code>,</li>
-            <li><b>Clé de stream :</b> votre identifiant.</li>
-          </ul>
-          <h5>Avec FFmpeg</h5>
-          <p>
-            <code>
-              ffmpeg -re -i mavideo.webm -vcodec libx264 -vprofile baseline
-              -acodec aac -strict -2 -f flv
-              rtmps://stream.adm.crans.org:1935/live/identifiant
-            </code>
-          </p>
-
-          <h4>Mentions légales</h4>
-          <p>
-            Le service de diffusion vidéo du Crans est un service d'hébergement
-            au sens de l'article 6, I, 2e de la loi 2004-575 du 21 juin 2004.
-            Conformément aux dispositions de l'article 6, II du même,
-            l'association Crans conserve les données de nature à permettre
-            l'identification des auteurs du contenu diffusé.
-            Ce service est hébergé par l'association Crans, au
-            61 Avenue du Président Wilson, 94235 Cachan Cedex, France.
-          </p>
-          <p>
-            <b>En cas de réclamation sur le contenu diffusé</b> de type
-            <code>https://stream.crans.org/identifiant</code>,
-            l'auteur peut être contacté par courrier à l'adresse
-            <code>identifiant@crans.org</code>.
-            La loi vous autorise à contacter directement l'hébergeur à
-            l'adresse suivante :
-            <pre>Association Crans - ENS Paris-Saclay<br/>Notification de Contenus Illicites<br/>61, Avenue du Président Wilson<br/>94235 Cachan Cedex<br/>France</pre>
-            Vous pouvez également envoyer directement vos réclamations par
-            courrier électronique à l'adresse <code>bureau[at]crans.org</code>.
-          </p>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <script src="lib/jquery/jquery.min.js"></script>
-  <script src="lib/bootstrap4/js/bootstrap.min.js"></script>
-  <script src="//unpkg.com/video.js@7/dist/video.min.js"></script>
-  <script>
-  // Get current stream
-  let streamId = window.location.pathname.split("/").pop()
-  if (streamId.length === 0) {
-    streamId = "crans"  // default on crans account
-  }
-  const streamUrl = `/hls/${streamId}.m3u8`
-  document.getElementById("linkStream").href = streamUrl
-
-  // Create player
-  const player = videojs('my-video', {
-    controls: true,
-    autoplay: true,
-    preload: 'auto',
-    muted: true,
-    fluid: true,
-    aspectRatio: '16:9'
-  })
-
-  function loadStream() {
-    fetch(streamUrl, {
-      method: 'HEAD',
-       cache: 'no-cache'
-    }).then((response) => {
-     if (response.ok) {
-        // Stream exists, load!
-        player.poster()
-        player.src({
-          type: 'application/x-mpegURL',
-           src: streamUrl
-        })
-      } else {
-        // Stream does not exist, alert!
-        player.poster('/no-stream.jpg')
-      }
-    })
-  }
-
-  $(document).ready(function () {
-    loadStream()
-
-    document.getElementById("refreshStream").addEventListener("click", function(){
-      loadStream()
-    })
-  })
-  </script>
-</body>
-</html>
diff --git a/roles/nginx-rtmp/files/no-stream.jpg b/roles/nginx-rtmp/files/no-stream.jpg
deleted file mode 100644
index af44c5a43ea0399422d9fe21c302a262c53f7a3e..0000000000000000000000000000000000000000
Binary files a/roles/nginx-rtmp/files/no-stream.jpg and /dev/null differ
diff --git a/roles/nginx-rtmp/handlers/main.yml b/roles/nginx-rtmp/handlers/main.yml
deleted file mode 100644
index 3b8f2d6927c32e1ccdb51d04f84b0944f2aa7d93..0000000000000000000000000000000000000000
--- a/roles/nginx-rtmp/handlers/main.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-
-- name: restart nginx
-  service:
-    name: nginx
-    state: restarted
-  become: true
diff --git a/roles/nginx-rtmp/tasks/main.yml b/roles/nginx-rtmp/tasks/main.yml
deleted file mode 100644
index a60c62cbe3bac0591a5cd35fd73b43449dfd4a9a..0000000000000000000000000000000000000000
--- a/roles/nginx-rtmp/tasks/main.yml
+++ /dev/null
@@ -1,59 +0,0 @@
----
-
-- name: Install nginx with rtmp module
-  apt:
-    name:
-      - nginx-full
-      - libnginx-mod-rtmp
-      - libjs-bootstrap4
-  register: apt_result
-  retries: 3
-  until: apt_result is succeeded
-  become: yes
-
-- name: Copy module and site configuration files
-  template:
-    src: "{{ item }}.j2"
-    dest: "/etc/{{ item }}"
-  loop:
-    - nginx/modules-available/60-rtmp.conf
-    - nginx/sites-available/stream
-  notify: restart nginx
-
-- name: Enable NGINX site
-  file:
-    src: /etc/nginx/sites-available/stream
-    dest: /etc/nginx/sites-enabled/stream
-    state: link
-  notify: restart nginx
-
-- name: Enable RTMP module
-  file:
-    src: /etc/nginx/modules-available/60-rtmp.conf
-    dest: /etc/nginx/modules-enabled/60-rtmp.conf
-    state: link
-  notify: restart nginx
-
-- name: Create site folder
-  file:
-    path: /var/www/stream/hls
-    state: directory
-
-- name: Copy index.html and assets
-  copy:
-    src: "{{ item }}"
-    dest: "/var/www/stream/{{ item }}"
-  loop:
-    - index.html
-    - no-stream.jpg
-
-- name: Link javascript libs
-  file:
-    src: /usr/share/javascript
-    dest: /var/www/stream/lib
-    state: link
-
-- name: Configure tmpfs
-  lineinfile:
-    path: /etc/fstab
-    line: tmpfs /var/www/stream/hls tmpfs rw,noexec,nodev,nosuid 0 0
diff --git a/roles/nginx-rtmp/templates/nginx/modules-available/60-rtmp.conf.j2 b/roles/nginx-rtmp/templates/nginx/modules-available/60-rtmp.conf.j2
deleted file mode 100644
index e780fb6c9a9842a58339015195642b734fad73d4..0000000000000000000000000000000000000000
--- a/roles/nginx-rtmp/templates/nginx/modules-available/60-rtmp.conf.j2
+++ /dev/null
@@ -1,18 +0,0 @@
-{{ ansible_header | comment }}
-
-rtmp {
-    server {
-        listen 1935;
-        chunk_size 4096;
-        application live {
-            live on;
-            hls on;
-            hls_path /var/www/stream/hls/;
-            hls_fragment 3;
-            hls_playlist_length 20;
-
-            record off;
-        }
-    }
-}
-
diff --git a/roles/nginx-rtmp/templates/nginx/sites-available/stream.j2 b/roles/nginx-rtmp/templates/nginx/sites-available/stream.j2
deleted file mode 100644
index 2a074c8da1ec704eaf1a416aaccf9cc1f0f3ed19..0000000000000000000000000000000000000000
--- a/roles/nginx-rtmp/templates/nginx/sites-available/stream.j2
+++ /dev/null
@@ -1,40 +0,0 @@
-{{ ansible_header | comment }}
-
-server {
-    listen 80;
-    listen [::]:80;
-
-    server_name {{ nginx_rtmp.uri }};
-
-    root /var/www/stream;
-    index index.html;
-
-    location / {
-        try_files $uri $uri/ /index.html;
-    }
-
-    location /hls {
-        # Disable cache
-        add_header Cache-Control no-cache;
-
-        # CORS setup
-        add_header 'Access-Control-Allow-Origin' '*' always;
-        add_header 'Access-Control-Expose-Headers' 'Content-Length';
-
-        # allow CORS preflight requests
-        if ($request_method = 'OPTIONS') {
-            add_header 'Access-Control-Allow-Origin' '*';
-            add_header 'Access-Control-Max-Age' 1728000;
-            add_header 'Content-Type' 'text/plain charset=UTF-8';
-            add_header 'Content-Length' 0;
-            return 204;
-        }
-
-        types {
-            application/dash+xml mpd;
-            application/vnd.apple.mpegurl m3u8;
-            video/mp2t ts;
-        }
-    }
-}
-
diff --git a/roles/nginx-mailman/handlers/main.yml b/roles/nginx/handlers/main.yml
similarity index 100%
rename from roles/nginx-mailman/handlers/main.yml
rename to roles/nginx/handlers/main.yml
diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c43f3a337b008b67c5b6ed9112a45ca768592a4a
--- /dev/null
+++ b/roles/nginx/tasks/main.yml
@@ -0,0 +1,128 @@
+---
+- name: Install NGINX
+  apt:
+    update_cache: true
+    name: nginx
+  register: apt_result
+  retries: 3
+  until: apt_result is succeeded
+
+- name: Copy proxypass snippets
+  template:
+    src: "nginx/snippets/options-proxypass.conf.j2"
+    dest: "/etc/nginx/snippets/options-proxypass.conf"
+    owner: root
+    group: root
+    mode: 0644
+
+- name: Copy SSL snippets
+  template:
+    src: "nginx/snippets/options-ssl.conf.j2"
+    dest: "/etc/nginx/snippets/options-ssl.{{ item.name }}.conf"
+    owner: root
+    group: root
+    mode: 0644
+  loop: "{{ nginx.ssl }}"
+
+- name: Disable default site
+  file:
+    dest: "/etc/nginx/sites-enabled/default"
+    state: absent
+
+- name: Copy reverse proxy sites
+  when: reverseproxy is defined
+  template:
+    src: "nginx/sites-available/{{ item }}.j2"
+    dest: "/etc/nginx/sites-available/{{ item }}"
+    owner: root
+    group: root
+    mode: 0644
+  loop:
+    - reverseproxy
+    - reverseproxy_redirect_dname
+    - redirect
+  notify: Reload nginx
+
+- name: Activate reverse proxy sites
+  when: reverseproxy is defined
+  file:
+    src: "/etc/nginx/sites-available/{{ item }}"
+    dest: "/etc/nginx/sites-enabled/{{ item }}"
+    owner: root
+    group: root
+    state: link
+  loop:
+    - reverseproxy
+    - reverseproxy_redirect_dname
+    - redirect
+  notify: Reload nginx
+  ignore_errors: "{{ ansible_check_mode }}"
+
+- name: Copy service nginx configuration
+  when: nginx.servers is defined and nginx.servers|length > 0
+  template:
+    src: "nginx/sites-available/service.j2"
+    dest: "/etc/nginx/sites-available/{{ nginx.service_name }}"
+    owner: root
+    group: root
+    mode: 0644
+  notify: Reload nginx
+
+- name: Activate local nginx service site
+  when: nginx.servers is defined and nginx.servers|length > 0
+  file:
+    src: "/etc/nginx/sites-available/{{ nginx.service_name }}"
+    dest: "/etc/nginx/sites-enabled/{{ nginx.service_name }}"
+    owner: root
+    group: root
+    state: link
+  notify: Reload nginx
+  ignore_errors: "{{ ansible_check_mode }}"
+
+- name: Copy 50x error page
+  template:
+    src: www/html/50x.html.j2
+    dest: /var/www/html/50x.html
+    owner: www-data
+    group: www-data
+    mode: 0644
+
+- name: Copy robots.txt file
+  when: nginx.deploy_robots_file
+  template:
+    src: www/html/robots.txt.j2
+    dest: /var/www/html/robots.txt
+    owner: www-data
+    group: www-data
+    mode: 0644
+
+- name: Install passwords
+  when: nginx.auth_passwd|length > 0
+  template:
+    src: nginx/passwd.j2
+    dest: /etc/nginx/passwd
+    mode: 0644
+
+- name: Copy 401 error page
+  when: nginx.auth_passwd|length > 0
+  template:
+    src: www/html/401.html.j2
+    dest: /var/www/html/401.html
+    owner: www-data
+    group: www-data
+    mode: 0644
+
+- name: Indicate role in motd
+  template:
+    src: update-motd.d/05-service.j2
+    dest: /etc/update-motd.d/05-nginx
+    mode: 0755
+
+- name: Clean old files
+  file:
+    path: "{{ item }}"
+    state: absent
+  loop:
+    - "/etc/nginx/snippets/options-ssl.conf"
+    - "/var/www/custom_401.html"
+    - "/var/www/robots.txt"
diff --git a/roles/nginx-reverseproxy/templates/letsencrypt/dhparam.j2 b/roles/nginx/templates/letsencrypt/dhparam.j2
similarity index 100%
rename from roles/nginx-reverseproxy/templates/letsencrypt/dhparam.j2
rename to roles/nginx/templates/letsencrypt/dhparam.j2
diff --git a/roles/nginx/templates/nginx/passwd.j2 b/roles/nginx/templates/nginx/passwd.j2
new file mode 100644
index 0000000000000000000000000000000000000000..75d0ff7c813da0f8599fe8a47beb90baad466397
--- /dev/null
+++ b/roles/nginx/templates/nginx/passwd.j2
@@ -0,0 +1,4 @@
+{{ ansible_header | comment }}
+{% for user, hash in nginx.auth_passwd.items() -%}
+{{ user }}:{{ hash }}
+{% endfor -%}
diff --git a/roles/nginx-reverseproxy/templates/nginx/sites-available/redirect.j2 b/roles/nginx/templates/nginx/sites-available/redirect.j2
similarity index 58%
rename from roles/nginx-reverseproxy/templates/nginx/sites-available/redirect.j2
rename to roles/nginx/templates/nginx/sites-available/redirect.j2
index 9cdb545bb715f629254783cd6841ed76877a0f6e..d40a3a4b49989496ef941b85c297ad12c498e056 100644
--- a/roles/nginx-reverseproxy/templates/nginx/sites-available/redirect.j2
+++ b/roles/nginx/templates/nginx/sites-available/redirect.j2
@@ -1,6 +1,6 @@
 {{ ansible_header | comment }}
 
-{% for site in nginx.redirect_sites %}
+{% for site in reverseproxy.redirect_sites %}
 # Redirect http://{{ site.from }} to http://{{ site.to }}
 server {
     listen 80;
@@ -8,6 +8,11 @@ server {
 
     server_name {{ site.from }};
 
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
     location / {
         return 302 http://{{ site.to }}$request_uri;
     }
@@ -21,7 +26,12 @@ server {
     server_name {{ site.from }};
 
     # SSL common conf
-    include "/etc/nginx/snippets/options-ssl.conf";
+    include "/etc/nginx/snippets/options-ssl.{{ site.ssl|default(nginx.default_ssl_domain) }}.conf";
+
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
 
     location / {
         return 302 https://{{ site.to }}$request_uri;
@@ -31,8 +41,8 @@ server {
 {% endfor %}
 
 {# Also redirect for DNAMEs #}
-{% for dname in nginx.redirect_dnames %}
-{% for site in nginx.redirect_sites %}
+{% for dname in reverseproxy.redirect_dnames %}
+{% for site in reverseproxy.redirect_sites %}
 {% set from = site.from | regex_replace('crans.org', dname) %}
 {% if from != site.from %}
 # Redirect http://{{ from }} to http://{{ site.to }}
@@ -42,6 +52,11 @@ server {
 
     server_name {{ from }};
 
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
     location / {
         return 302 http://{{ site.to }}$request_uri;
     }
@@ -55,7 +70,12 @@ server {
     server_name {{ from }};
 
     # SSL common conf
-    include "/etc/nginx/snippets/options-ssl.conf";
+    include "/etc/nginx/snippets/options-ssl.{{ site.ssl|default(nginx.default_ssl_domain) }}.conf";
+
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
 
     location / {
         return 302 https://{{ site.to }}$request_uri;
diff --git a/roles/nginx-reverseproxy/templates/nginx/sites-available/reverseproxy.j2 b/roles/nginx/templates/nginx/sites-available/reverseproxy.j2
similarity index 76%
rename from roles/nginx-reverseproxy/templates/nginx/sites-available/reverseproxy.j2
rename to roles/nginx/templates/nginx/sites-available/reverseproxy.j2
index 0898da05222c522210d390831f00c521f9d24dd0..27013aabce4cc473579284e59d49c32b8635d4d0 100644
--- a/roles/nginx-reverseproxy/templates/nginx/sites-available/reverseproxy.j2
+++ b/roles/nginx/templates/nginx/sites-available/reverseproxy.j2
@@ -7,7 +7,7 @@ map $http_upgrade $connection_upgrade {
     ''      close;
 }
 
-{% for site in nginx.reverseproxy_sites %}
+{% for site in reverseproxy.reverseproxy_sites %}
 # Redirect http://{{ site.from }} to https://{{ site.from }}
 server {
     listen 80;
@@ -15,6 +15,11 @@ server {
 
     server_name {{ site.from }};
 
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
     location / {
         return 302 https://$host$request_uri;
     }
@@ -28,7 +33,7 @@ server {
     server_name {{ site.from }};
 
     # SSL common conf
-    include "/etc/nginx/snippets/options-ssl.conf";
+    include "/etc/nginx/snippets/options-ssl.{{ site.ssl|default(nginx.default_ssl_domain) }}.conf";
 
     # Log into separate log files
     access_log      /var/log/nginx/{{ site.from }}.log;
@@ -43,8 +48,9 @@ server {
         root /var/www/html;
     }
 
-    set_real_ip_from 10.231.136.0/24;
-    set_real_ip_from 2a0c:700:0:2::/64;
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
     real_ip_header P-Real-Ip;
 
     location / {
diff --git a/roles/nginx-reverseproxy/templates/nginx/sites-available/reverseproxy_redirect_dname.j2 b/roles/nginx/templates/nginx/sites-available/reverseproxy_redirect_dname.j2
similarity index 58%
rename from roles/nginx-reverseproxy/templates/nginx/sites-available/reverseproxy_redirect_dname.j2
rename to roles/nginx/templates/nginx/sites-available/reverseproxy_redirect_dname.j2
index db2084a433ce387349debb0a82604d8a3a553e1b..0b39022fb2f440a4423987e37cde8e07f83ae2e1 100644
--- a/roles/nginx-reverseproxy/templates/nginx/sites-available/reverseproxy_redirect_dname.j2
+++ b/roles/nginx/templates/nginx/sites-available/reverseproxy_redirect_dname.j2
@@ -1,7 +1,7 @@
 {{ ansible_header | comment }}
 
-{% for dname in nginx.redirect_dnames %}
-{% for site in nginx.reverseproxy_sites %}
+{% for dname in reverseproxy.redirect_dnames %}
+{% for site in reverseproxy.reverseproxy_sites %}
 {% set from = site.from | regex_replace('crans.org', dname) %}
 {% set to = site.from %}
 {% if from != site.from %}
@@ -12,6 +12,11 @@ server {
 
     server_name {{ from }};
 
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
     location / {
         return 302 http://{{ to }}$request_uri;
     }
@@ -25,7 +30,12 @@ server {
     server_name {{ from }};
 
     # SSL common conf
-    include "/etc/nginx/snippets/options-ssl.conf";
+    include "/etc/nginx/snippets/options-ssl.{{ site.ssl|default(nginx.default_ssl_domain) }}.conf";
+
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
 
     location / {
         return 302 https://{{ to }}$request_uri;
diff --git a/roles/nginx/templates/nginx/sites-available/service.j2 b/roles/nginx/templates/nginx/sites-available/service.j2
new file mode 100644
index 0000000000000000000000000000000000000000..7c7244ab393cfc1ca929bbb5f39b3d5e1a849235
--- /dev/null
+++ b/roles/nginx/templates/nginx/sites-available/service.j2
@@ -0,0 +1,132 @@
+{{ ansible_header | comment }}
+
+# Automatic Connection header for WebSocket support
+# See http://nginx.org/en/docs/http/websocket.html
+map $http_upgrade $connection_upgrade {
+    default upgrade;
+    ''      close;
+}
+
+{% for upstream in nginx.upstreams -%}
+upstream {{ upstream.name }} {
+    # Path of the server
+    server {{ upstream.server }};
+}
+{% endfor -%}
+
+{% if nginx.default_ssl_server -%}
+# Redirect all services to the main site
+server {
+    listen 443 default_server ssl;
+    listen [::]:443 default_server ssl;
+    include "/etc/nginx/snippets/options-ssl.{{ nginx.default_ssl_domain }}.conf";
+
+    server_name _;
+    charset utf-8;
+
+    # Hide Nginx version
+    server_tokens off;
+
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
+    location / {
+        return 302 https://{{ nginx.default_ssl_server }}$request_uri;
+    }
+}
+{% endif -%}
+
+{% if nginx.default_server -%}
+# Redirect all services to the main site
+server {
+    listen 80 default_server;
+    listen [::]:80 default_server;
+
+    server_name _;
+    charset utf-8;
+
+    # Hide Nginx version
+    server_tokens off;
+
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
+    location / {
+        return 302 http://{{ nginx.default_server }}$request_uri;
+    }
+}
+{% endif -%}
+
+{% for server in nginx.servers %}
+{% if server.ssl is defined and server.ssl -%}
+# Redirect HTTP to HTTPS
+server {
+    listen 80{% if server.default is defined and server.default %} default_server{% endif %};
+    listen [::]:80{% if server.default is defined and server.default %} default_server{% endif %};
+
+    server_name {{ server.server_name|join(" ") }};
+    charset utf-8;
+
+    # Hide Nginx version
+    server_tokens off;
+
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
+    location / {
+        return 302 https://$host$request_uri;
+    }
+}
+{% endif -%}
+
+server {
+    {% if server.ssl is defined and server.ssl -%}
+    listen 443{% if server.default is defined and server.default %} default_server{% endif %} ssl;
+    listen [::]:443{% if server.default is defined and server.default %} default_server{% endif %} ssl;
+    include "/etc/nginx/snippets/options-ssl.{{ server.ssl }}.conf";
+    {% else -%}
+    listen 80 default;
+    listen [::]:80 default;
+    {% endif -%}
+
+    server_name {{ server.server_name|join(" ") }};
+    charset utf-8;
+
+    # Hide Nginx version
+    server_tokens off;
+
+{% for realip in nginx.real_ip_from %}
+    set_real_ip_from {{ realip }};
+{% endfor %}
+    real_ip_header P-Real-Ip;
+
+    {% if server.root is defined %}root {{ server.root }};{% endif %}
+    {% if server.index is defined %}index {{ server.index|join(" ") }};{% endif %}
+
+    {% if server.access_log is defined %}access_log {{ server.access_log }};{% endif %}
+    {% if server.error_log is defined %}error_log {{ server.error_log }};{% endif %}
+
+{% if server.additional_params is defined %}
+{% for param in server.additional_params %}
+    {{ param }};
+{% endfor %}
+{% endif %}
+
+{% if server.locations is defined %}
+{% for location in server.locations %}
+    location {{ location.filter }} {
+{% for param in location.params %}
+        {{ param }};
+{% endfor %}
+    }
+
+{% endfor %}
+{% endif %}
+}
+{% endfor %}
diff --git a/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2~ b/roles/nginx/templates/nginx/snippets/fastcgi.conf.j2
similarity index 91%
rename from roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2~
rename to roles/nginx/templates/nginx/snippets/fastcgi.conf.j2
index 3ce2f9237d596cbd5acc9336c5e2040830aa49c0..d3215c7fa99aabdad9fe87c45bf66fb5f6bffd10 100644
--- a/roles/nginx-mailman/templates/nginx/snippets/fastcgi-mailman.conf.j2~
+++ b/roles/nginx/templates/nginx/snippets/fastcgi.conf.j2
@@ -15,4 +15,4 @@ fastcgi_param PATH_INFO $path_info;
 fastcgi_intercept_errors on;
 
 include /etc/nginx/fastcgi.conf;
-fastcgi_pass unix:/var/run/fcgiwrap.socket;
\ No newline at end of file
+fastcgi_pass unix:/var/run/fcgiwrap.socket;
diff --git a/roles/nginx-reverseproxy/templates/nginx/snippets/options-proxypass.conf.j2 b/roles/nginx/templates/nginx/snippets/options-proxypass.conf.j2
similarity index 100%
rename from roles/nginx-reverseproxy/templates/nginx/snippets/options-proxypass.conf.j2
rename to roles/nginx/templates/nginx/snippets/options-proxypass.conf.j2
diff --git a/roles/nginx-reverseproxy/templates/nginx/snippets/options-ssl.conf.j2 b/roles/nginx/templates/nginx/snippets/options-ssl.conf.j2
similarity index 79%
rename from roles/nginx-reverseproxy/templates/nginx/snippets/options-ssl.conf.j2
rename to roles/nginx/templates/nginx/snippets/options-ssl.conf.j2
index 1a9273a814ebb14034f4d2503b7bd5940d52424d..c980c90bc2b05345682ebcf5955d830210364971 100644
--- a/roles/nginx-reverseproxy/templates/nginx/snippets/options-ssl.conf.j2
+++ b/roles/nginx/templates/nginx/snippets/options-ssl.conf.j2
@@ -1,7 +1,7 @@
 {{ ansible_header | comment }}
 
-ssl_certificate {{ nginx.ssl.cert }};
-ssl_certificate_key {{ nginx.ssl.cert_key }};
+ssl_certificate {{ item.cert }};
+ssl_certificate_key {{ item.cert_key }};
 ssl_session_timeout 1d;
 ssl_session_cache shared:MozSSL:10m;
 ssl_session_tickets off;
@@ -13,5 +13,5 @@ ssl_prefer_server_ciphers off;
 # Enable OCSP Stapling, point to certificate chain
 ssl_stapling on;
 ssl_stapling_verify on;
-ssl_trusted_certificate {{ nginx.ssl.trusted_cert }};
+ssl_trusted_certificate {{ item.trusted_cert }};
 
diff --git a/roles/nginx-mailman/templates/update-motd.d/05-service.j2 b/roles/nginx/templates/update-motd.d/05-service.j2
similarity index 100%
rename from roles/nginx-mailman/templates/update-motd.d/05-service.j2
rename to roles/nginx/templates/update-motd.d/05-service.j2
diff --git a/roles/nginx-mailman/templates/var/www/custom_401.html.j2 b/roles/nginx/templates/www/html/401.html.j2
similarity index 100%
rename from roles/nginx-mailman/templates/var/www/custom_401.html.j2
rename to roles/nginx/templates/www/html/401.html.j2
diff --git a/roles/nginx-reverseproxy/templates/www/html/50x.html.j2 b/roles/nginx/templates/www/html/50x.html.j2
similarity index 100%
rename from roles/nginx-reverseproxy/templates/www/html/50x.html.j2
rename to roles/nginx/templates/www/html/50x.html.j2
diff --git a/roles/nginx/templates/www/html/robots.txt.j2 b/roles/nginx/templates/www/html/robots.txt.j2
new file mode 100644
index 0000000000000000000000000000000000000000..1f53798bb4fe33c86020be7f10c44f29486fd190
--- /dev/null
+++ b/roles/nginx/templates/www/html/robots.txt.j2
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/roles/roundcube/handlers/main.yml b/roles/roundcube/handlers/main.yml
deleted file mode 100644
index 2e593d346511aca262e28a50bad99d1bad99530e..0000000000000000000000000000000000000000
--- a/roles/roundcube/handlers/main.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Restart nginx
-  service:
-    name: nginx
-    state: restarted
diff --git a/roles/roundcube/tasks/main.yml b/roles/roundcube/tasks/main.yml
index 18745b552ccb6f31c9c89491659f474e014aff3c..a67f90b8f5572659a75966b3ad7e0850aad001a2 100644
--- a/roles/roundcube/tasks/main.yml
+++ b/roles/roundcube/tasks/main.yml
@@ -4,7 +4,6 @@
     update_cache: true
     install_recommends: false
     name:
-      - nginx
       - roundcube
       - roundcube-pgsql
       - roundcube-plugins
@@ -52,19 +51,6 @@
   loop: "{{ roundcube.plugins }}"
   when: item.repo is defined
 
-- name: Copy NGINX site
-  template:
-    src: nginx/roundcube.j2
-    dest: /etc/nginx/sites-available/roundcube
-  notify: Restart nginx
-
-- name: Activate NGINX site
-  file:
-    src: /etc/nginx/sites-available/roundcube
-    dest: /etc/nginx/sites-enabled/roundcube
-    state: link
-  notify: Restart nginx
-
 - name: Indicate role in motd
   template:
     src: update-motd.d/05-service.j2
diff --git a/roles/roundcube/templates/nginx/roundcube.j2 b/roles/roundcube/templates/nginx/roundcube.j2
deleted file mode 100644
index ce34cead1159a09015c976b9d2c370a2523b3639..0000000000000000000000000000000000000000
--- a/roles/roundcube/templates/nginx/roundcube.j2
+++ /dev/null
@@ -1,28 +0,0 @@
-{{ ansible_header | comment }}
-
-server {
-    listen roundcube.adm.crans.org:80;
-    listen [2a0c:700:0:2:6809:acff:fe67:47e6]:80;
-
-    server_name roundcube.adm.crans.org;
-
-    root /var/lib/roundcube;
-
-    index index.php index.htm index.html;
-    try_files $uri $uri/ /index.php?q=$uri&$args;
-
-    location ~ \.php$ {
-        include snippets/fastcgi-php.conf;
-        fastcgi_buffer_size 128k;
-        fastcgi_buffers 4 256k;
-        fastcgi_busy_buffers_size 256k;
-        fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
-        include fastcgi_params;
-    }
-
-    set_real_ip_from 10.231.136.0/24;
-    set_real_ip_from 2a0c:700:0:2::/64;
-    real_ip_header P-Real-Ip;
-
-    client_max_body_size 10G;
-}
diff --git a/roles/statping/handlers/main.yml b/roles/statping/handlers/main.yml
index c395b3550e78f0f19b7a104c505a4fbda776ae2e..4868a5ce4be43878c65a0db2de27dc5c404d580c 100644
--- a/roles/statping/handlers/main.yml
+++ b/roles/statping/handlers/main.yml
@@ -1,9 +1,4 @@
 ---
-- name: Restart nginx
-  service:
-    name: nginx
-    state: restarted
-
 - name: Restart statping
   service:
     name: statping
diff --git a/roles/statping/tasks/main.yml b/roles/statping/tasks/main.yml
index bc7487ada3efcc4e56c26569c9f86fa9ec713187..03578d7083a8ec1bbabd83600c3f4faf5ffcb017 100644
--- a/roles/statping/tasks/main.yml
+++ b/roles/statping/tasks/main.yml
@@ -29,19 +29,6 @@
     enabled: true
     state: started
 
-- name: Configure statping nginx site
-  template:
-    src: nginx/sites-available/status.j2
-    dest: /etc/nginx/sites-available/status
-  notify: Restart nginx
-
-- name: Activate statping nginx site
-  file:
-    src: /etc/nginx/sites-available/status
-    dest: /etc/nginx/sites-enabled/status
-    state: link
-  notify: Restart nginx
-
 - name: Indicate role in motd
   template:
     src: update-motd.d/05-service.j2
diff --git a/roles/statping/templates/nginx/sites-available/status.j2 b/roles/statping/templates/nginx/sites-available/status.j2
deleted file mode 100644
index 4eb80feda2ae7116dff3ecd4bc5085d6879438ad..0000000000000000000000000000000000000000
--- a/roles/statping/templates/nginx/sites-available/status.j2
+++ /dev/null
@@ -1,15 +0,0 @@
-{{ ansible_header | comment }}
-
-server {
-    listen 80;
-    listen [::]:80;
-    server_name status.crans.org;
-
-    access_log /var/log/nginx/status.log combined;
-    error_log /var/log/nginx/status.error.log;
-
-    location / {
-        proxy_pass http://127.0.0.1:8080;
-        proxy_redirect off;
-    }
-}
diff --git a/roles/thelounge/handlers/main.yml b/roles/thelounge/handlers/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..95b737c96fe0d5a97867b369f373df356b378b45
--- /dev/null
+++ b/roles/thelounge/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Restart The Lounge
+  systemd:
+    name: thelounge
+    state: restarted
diff --git a/roles/thelounge/tasks/main.yml b/roles/thelounge/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7403a1dc834b0c277895910531ed7aa16625ed59
--- /dev/null
+++ b/roles/thelounge/tasks/main.yml
@@ -0,0 +1,32 @@
+---
+- name: Install NodeJS
+  apt:
+    update_cache: true
+    install_recommends: false
+    name: nodejs
+  register: apt_result
+  retries: 3
+  until: apt_result is succeeded
+
+- name: Download The Lounge packet
+  get_url:
+    url: https://github.com/thelounge/thelounge/releases/download/v4.2.0/thelounge_4.2.0_all.deb
+    dest: /var/cache/apt/archives/thelounge_4.2.0_all.deb
+    checksum: sha512:10d17c199fef595c46ba55f36ab7aa0a7469448603482eb780284e05532f5d69302d47bdc9c558badc30b0a026767a24b7065a4fe8d7c865f1737b0041420208
+    owner: root
+    group: root
+    mode: 0644
+
+- name: Install The Lounge from the deb package
+  apt:
+    deb: /var/cache/apt/archives/thelounge_4.2.0_all.deb
+  notify: Restart The Lounge
+
+- name: Deploy The Lounge configuration
+  template:
+    src: config.js.j2
+    dest: /etc/thelounge/config.js
+    owner: thelounge
+    group: thelounge
+    mode: 0660
+  notify: Restart The Lounge
diff --git a/roles/thelounge/templates/config.js.j2 b/roles/thelounge/templates/config.js.j2
new file mode 100644
index 0000000000000000000000000000000000000000..87ce22dad10c6d6af5f8f9529b54a9379953dc3d
--- /dev/null
+++ b/roles/thelounge/templates/config.js.j2
@@ -0,0 +1,470 @@
+"use strict";
+
+module.exports = {
+	// ## Server settings
+
+	// ### `public`
+	//
+	// When set to `true`, The Lounge starts in public mode. When set to `false`,
+	// it starts in private mode.
+	//
+	// - A **public server** does not require authentication. Anyone can connect
+	//   to IRC networks in this mode. All IRC connections and channel
+	//   scrollbacks are lost when a user leaves the client.
+	// - A **private server** requires users to log in. Their IRC connections are
+	//   kept even when they are not using or logged in to the client. All joined
+	//   channels and scrollbacks are available when they come back.
+	//
+	// This value is set to `false` by default.
+	public: {{ thelounge.public }},
+
+	// ### `host`
+	//
+	// IP address or hostname for the web server to listen to. For example, set it
+	// to `"127.0.0.1"` to accept connections from localhost only.
+	//
+	// For UNIX domain sockets, use `"unix:/absolute/path/to/file.sock"`.
+	//
+	// This value is set to `undefined` by default to listen on all interfaces.
+	host: {{ thelounge.host }},
+
+	// ### `port`
+	//
+	// Set the port to listen to.
+	//
+	// This value is set to `9000` by default.
+	port: 9000,
+
+	// ### `bind`
+	//
+	// Set the local IP to bind to for outgoing connections.
+	//
+	// This value is set to `undefined` by default to let the operating system
+	// pick its preferred one.
+	bind: undefined,
+
+	// ### `reverseProxy`
+	//
+	// When set to `true`, The Lounge is marked as served behind a reverse proxy
+	// and will honor the `X-Forwarded-For` header.
+	//
+	// This value is set to `false` by default.
+	reverseProxy: {{ thelounge.reverseProxy }},
+
+	// ### `maxHistory`
+	//
+	// Defines the maximum number of history lines that will be kept in memory per
+	// channel/query, in order to reduce the memory usage of the server. Setting
+	// this to `-1` will keep unlimited amount.
+	//
+	// This value is set to `10000` by default.
+	maxHistory: 10000,
+
+	// ### `https`
+	//
+	// These settings are used to run The Lounge's web server using encrypted TLS.
+	//
+	// If you want more control over the webserver,
+	// [use a reverse proxy instead](https://thelounge.chat/docs/guides/reverse-proxies).
+	//
+	// The available keys for the `https` object are:
+	//
+	// - `enable`: when set to `false`, HTTPS support is disabled
+	//    and all other values are ignored.
+	// - `key`: Path to the private key file.
+	// - `certificate`: Path to the certificate.
+	// - `ca`: Path to the CA bundle.
+	//
+	// The value of `enable` is set to `false` to disable HTTPS by default, in
+	// which case the other two string settings are ignored.
+	https: {
+		enable: false,
+		key: "",
+		certificate: "",
+		ca: "",
+	},
+
+	// ## Client settings
+
+	// ### `theme`
+	//
+	// Set the default theme to serve to new users. They will be able to select a
+	// different one in their client settings among those available.
+	//
+	// The Lounge ships with two themes (`default` and `morning`) and can be
+	// extended by installing more themes. Read more about how to manage them
+	// [here](https://thelounge.chat/docs/guides/theme-creation).
+	//
+	// This value needs to be the package name and not the display name. For
+	// example, the value for Morning would be `morning`, and the value for
+	// Solarized would be `thelounge-theme-solarized`.
+	//
+	// This value is set to `"default"` by default.
+	theme: "default",
+
+	// ### `prefetch`
+	//
+	// When set to `true`, The Lounge will load thumbnails and site descriptions
+	// from URLs posted in channels and private messages.
+	//
+	// This value is set to `false` by default.
+	prefetch: true,
+
+	// ### `disableMediaPreview`
+	//
+	// When set to `true`, The Lounge will not preview media (images, video and
+	// audio) hosted on third-party sites. This ensures the client does not
+	// make any requests to external sites. If `prefetchStorage` is enabled,
+	// images proxied via the The Lounge will be previewed.
+	//
+	// This has no effect if `prefetch` is set to `false`.
+	//
+	// This value is set to `false` by default.
+	disableMediaPreview: false,
+
+	// ### `prefetchStorage`
+
+	// When set to `true`, The Lounge will store and proxy prefetched images and
+	// thumbnails on the filesystem rather than directly display the content at
+	// the original URLs.
+	//
+	// This option primarily exists to resolve mixed content warnings by not
+	// loading images from http hosts. This option does not work for video
+	// or audio as The Lounge will only load these from https hosts.
+	//
+	// If storage is enabled, The Lounge will fetch and store images and thumbnails
+	// in the `${THELOUNGE_HOME}/storage` folder.
+	//
+	// Images are deleted when they are no longer referenced by any message
+	// (controlled by `maxHistory`), and the folder is cleaned up when The Lounge
+	// restarts.
+	//
+	// This value is set to `false` by default.
+	prefetchStorage: true,
+
+	// ### `prefetchMaxImageSize`
+	//
+	// When `prefetch` is enabled, images will only be displayed if their file
+	// size does not exceed this limit.
+	//
+	// This value is set to `2048` kilobytes by default.
+	prefetchMaxImageSize: 2048,
+
+	// ### `fileUpload`
+	//
+	// Allow uploading files to the server hosting The Lounge.
+	//
+	// Files are stored in the `${THELOUNGE_HOME}/uploads` folder, do not expire,
+	// and are not removed by The Lounge. This may cause issues depending on your
+	// hardware, for example in terms of disk usage.
+	//
+	// The available keys for the `fileUpload` object are:
+	//
+	// - `enable`: When set to `true`, files can be uploaded on the client with a
+	//   drag-and-drop or using the upload dialog.
+	// - `maxFileSize`: When file upload is enabled, users sending files above
+	//   this limit will be prompted with an error message in their browser. A value of
+	//   `-1` disables the file size limit and allows files of any size. **Use at
+	//   your own risk.** This value is set to `10240` kilobytes by default.
+	// - `baseUrl`: If you want change the URL where uploaded files are accessed,
+	//   you can set this option to `"https://example.com/folder/"` and the final URL
+	//   would look like `"https://example.com/folder/aabbccddeeff1234/name.png"`.
+	//   If you use this option, you must have a reverse proxy configured,
+	//   to correctly proxy the uploads URLs back to The Lounge.
+	//   This value is set to `null` by default.
+	fileUpload: {
+		enable: false,
+		maxFileSize: 10240,
+		baseUrl: null,
+	},
+
+	// ### `transports`
+	//
+	// Set `socket.io` transports.
+	//
+	// This value is set to `["polling", "websocket"]` by default.
+	transports: ["polling", "websocket"],
+
+	// ### `leaveMessage`
+	//
+	// Set users' default `quit` and `part` messages if they are not providing
+	// one.
+	//
+	// This value is set to `"The Lounge - https://thelounge.chat"` by
+	// default.
+	leaveMessage: "The Lounge - https://thelounge.chat",
+
+	// ## Default network
+
+	// ### `defaults`
+	//
+	// Specifies default network information that will be used as placeholder
+	// values in the *Connect* window.
+	//
+	// The available keys for the `defaults` object are:
+	//
+	// - `name`: Name to display in the channel list of The Lounge. This value is
+	//   not forwarded to the IRC network.
+	// - `host`: IP address or hostname of the IRC server.
+	// - `port`: Usually 6667 for unencrypted connections and 6697 for
+	//   connections encrypted with TLS.
+	// - `password`: Connection password. If the server supports SASL capability,
+	//   then this password will be used in SASL authentication.
+	// - `tls`: Enable TLS connections
+	// - `rejectUnauthorized`: Whether the server certificate should be verified
+	//   against the list of supplied Certificate Authorities (CAs) by your
+	//   Node.js installation.
+	// - `nick`: Nick name. Percent signs (`%`) will be replaced by random
+	//   numbers from 0 to 9. For example, `Guest%%%` may become `Guest123`.
+	// - `username`: User name.
+	// - `realname`: Real name.
+	// - `join`: Comma-separated list of channels to auto-join once connected.
+	//
+	// This value is set to connect to the official channel of The Lounge on
+	// Freenode by default:
+	//
+	// ```js
+	// defaults: {
+	//   name: "Freenode",
+	//   host: "chat.freenode.net",
+	//   port: 6697,
+	//   password: "",
+	//   tls: true,
+	//   rejectUnauthorized: true,
+	//   nick: "thelounge%%",
+	//   username: "thelounge",
+	//   realname: "The Lounge User",
+	//   join: "#thelounge"
+	// }
+	// ```
+	defaults: {
+		name: "{{ thelounge.irc.name }}",
+		host: "{{ thelounge.irc.host }}",
+		port: {{ thelounge.irc.port }},
+		password: "{{ thelounge.irc.password }}",
+		tls: {{ thelounge.irc.tls }},
+		rejectUnauthorized: {{ thelounge.irc.rejectUnauthorized }},
+		nick: "{{ thelounge.irc.nick }}",
+		username: "{{ thelounge.irc.username }}",
+		realname: "{{ thelounge.irc.realname }}",
+		join: "{{ thelounge.irc.join }}",
+	},
+
+	// ### `lockNetwork`
+	//
+	// When set to `true`, users will not be able to modify host, port and TLS
+	// settings and will be limited to the configured network.
+	// These fields will also be hidden from the UI.
+	//
+	// This value is set to `false` by default.
+	lockNetwork: false,
+
+	// ## User management
+
+	// ### `messageStorage`
+
+	// The Lounge can log user messages, for example to access them later or to
+	// reload messages on server restart.
+
+	// Set this array with one or multiple values to enable logging:
+	// - `text`: Messages per network and channel will be stored as text files.
+	//   **Messages will not be reloaded on restart.**
+	// - `sqlite`: Messages are stored in SQLite database files, one per user.
+	//
+	// Logging can be disabled globally by setting this value to an empty array
+	// `[]`. Logging is also controlled per user individually in the `log` key of
+	// their JSON configuration file.
+	//
+	// This value is set to `["sqlite", "text"]` by default.
+	messageStorage: ["sqlite", "text"],
+
+	// ### `useHexIp`
+	//
+	// When set to `true`, users' IP addresses will be encoded as hex.
+	//
+	// This is done to share the real user IP address with the server for host
+	// masking purposes. This is encoded in the `username` field and only supports
+	// IPv4.
+	//
+	// This value is set to `false` by default.
+	useHexIp: false,
+
+	// ## WEBIRC support
+	//
+	// When enabled, The Lounge will pass the connecting user's host and IP to the
+	// IRC server. Note that this requires to obtain a password from the IRC
+	// network that The Lounge will be connecting to and generally involves a lot
+	// of trust from the network you are connecting to.
+	//
+	// There are 2 ways to configure the `webirc` setting:
+	//
+	// - **Basic**: an object where keys are IRC hosts and values are passwords.
+	//   For example:
+	//
+	//   ```json
+	//   webirc: {
+	//     "irc.example.net": "thisiswebircpassword1",
+	//     "irc.example.org": "thisiswebircpassword2",
+	//   },
+	//   ```
+	//
+	// - **Advanced**: an object where keys are IRC hosts and values are functions
+	//   that take two arguments (`webircObj`, `network`) and return an
+	//   object to be directly passed to `irc-framework`. `webircObj` contains the
+	//   generated object which you can modify. For example:
+	//
+	//   ```js
+	//   webirc: {
+	//     "irc.example.com": (webircObj, network) => {
+	//       webircObj.password = "thisiswebircpassword";
+	//       webircObj.hostname = `webirc/${webircObj.hostname}`;
+	//       return webircObj;
+	//     },
+	//   },
+	//   ```
+	//
+	// This value is set to `null` to disable WEBIRC by default.
+	webirc: null,
+
+	// ## identd and oidentd support
+
+	// ### `identd`
+	//
+	// Run The Lounge with `identd` support.
+	//
+	// The available keys for the `identd` object are:
+	//
+	// - `enable`: When `true`, the identd daemon runs on server start.
+	// - `port`: Port to listen for ident requests.
+	//
+	// The value of `enable` is set to `false` to disable `identd` support by
+	// default, in which case the value of `port` is ignored. The default value of
+	// `port` is 113.
+	identd: {
+		enable: false,
+		port: 113,
+	},
+
+	// ### `oidentd`
+	//
+	// When this setting is a string, this enables `oidentd` support using the
+	// configuration file located at the given path.
+	//
+	// This is set to `null` by default to disable `oidentd` support.
+	oidentd: {{ thelounge.oidentd }},
+
+	// ## LDAP support
+
+	// These settings enable and configure LDAP authentication.
+	//
+	// They are only being used in private mode. To know more about private mode,
+	// see the `public` setting above.
+
+	//
+	// The authentication process works as follows:
+	//
+	// 1. The Lounge connects to the LDAP server with its system credentials.
+	// 2. It performs an LDAP search query to find the full DN associated to the
+	//    user requesting to log in.
+	// 3. The Lounge tries to connect a second time, but this time using the
+	//    user's DN and password. Authentication is validated if and only if this
+	//    connection is successful.
+	//
+	// The search query takes a couple of parameters in `searchDN`:
+	//
+	// - a base DN `searchDN/base`. Only children nodes of this DN will be likely
+	//   be returned;
+	// - a search scope `searchDN/scope` (see LDAP documentation);
+	// - the query itself, built as `(&(<primaryKey>=<username>) <filter>)`
+	//   where `<username>` is the user name provided in the log in request,
+	//   `<primaryKey>` is provided by the config and `<filter>` is a filtering
+	//   complement also given in the config, to filter for instance only for
+	//   nodes of type `inetOrgPerson`, or whatever LDAP search allows.
+	//
+	// Alternatively, you can specify the `bindDN` parameter. This will make The
+	// Lounge ignore `searchDN` options and assume that the user DN is always
+	// `<bindDN>,<primaryKey>=<username>`, where `<username>` is the user name
+	// provided in the log in request, and `<bindDN>` and `<primaryKey>` are
+	// provided by the configuration.
+	//
+	// The available keys for the `ldap` object are:
+	ldap: {
+		// - `enable`: when set to `false`, LDAP support is disabled and all other
+		//   values are ignored.
+		enable: {{ thelounge.ldap_enable }},
+{% if thelounge.ldap_enable == "true" %}
+		// - `url`: A url of the form `ldaps://<ip>:<port>`.
+		//   For plain connections, use the `ldap` scheme.
+		url: "{{ thelounge.ldap.url }}",
+
+		// - `tlsOptions`: LDAP connection TLS options (only used if scheme is
+		//   `ldaps://`). It is an object whose values are Node.js' `tls.connect()`
+		//   options. It is set to `{}` by default.
+		//   For example, this option can be used in order to force the use of IPv6:
+		//   ```js
+		//   {
+		//     host: 'my::ip::v6',
+		//     servername: 'example.com'
+		//   }
+		//   ```
+		tlsOptions: {},
+
+		// - `primaryKey`: LDAP primary key. It is set to `"uid"` by default.
+		primaryKey: "{{ thelounge.ldap.primaryKey }}",
+
+		// - `baseDN`: LDAP base DN, alternative to `searchDN`. For example, set it
+		//   to `"ou=accounts,dc=example,dc=com"`.
+		//   When unset, the LDAP auth logic with use `searchDN` instead to locate users.
+
+		// - `searchDN`: LDAP search DN settings. This defines the procedure by
+		//   which The Lounge first looks for the user DN before authenticating them.
+		//   It is ignored if `baseDN` is specified. It is an object with the
+		//   following keys:
+		searchDN: {
+			//   - `rootDN`: This bind DN is used to query the server for the DN of
+			//     the user. This is supposed to be a system user that has access in
+			//     read-only to the DNs of the people that are allowed to log in.
+			//     It is set to `"cn=thelounge,ou=system-users,dc=example,dc=com"` by
+			//     default.
+			rootDN: "{{ thelounge.ldap.rootDN }}",
+
+			//   - `rootPassword`: Password of The Lounge LDAP system user.
+			rootPassword: "{{ thelounge.ldap.rootPassword }}",
+
+			//   - `ldapFilter`: it is set to `"(objectClass=person)(memberOf=ou=accounts,dc=example,dc=com)"`
+			//     by default.
+			filter: "{{ thelounge.ldap.filter }}",
+
+			//   - `base`: LDAP search base (search only within this node). It is set
+			//     to `"dc=example,dc=com"` by default.
+			base: "{{ thelounge.ldap.base }}",
+
+			//   - `scope`: LDAP search scope. It is set to `"sub"` by default.
+			scope: "{{ thelounge.ldap.scope }}",
+		},
+{% endif %}
+	},
+
+	// ## Debugging settings
+
+	// The `debug` object contains several settings to enable debugging in The
+	// Lounge. Use them to learn more about an issue you are noticing but be aware
+	// this may produce more logging or may affect connection performance so it is
+	// not recommended to use them by default.
+	//
+	// All values in the `debug` object are set to `false`.
+	debug: {
+		// ### `debug.ircFramework`
+		//
+		// When set to true, this enables extra debugging output provided by
+		// [`irc-framework`](https://github.com/kiwiirc/irc-framework), the
+		// underlying IRC library for Node.js used by The Lounge.
+		ircFramework: false,
+
+		// ### `debug.raw`
+		//
+		// When set to `true`, this enables logging of raw IRC messages into each
+		// server window, displayed on the client.
+		raw: false,
+	},
+};