diff --git a/README.md b/README.md
index 2b3758a..e4412c5 100644
--- a/README.md
+++ b/README.md
@@ -17,3 +17,17 @@ ansible-playbook -i inventory.yml --ask-vault-pass main.yml
 ```
 
 You need to provide a user with sudo rights and the vault password.
+
+## HTTPS ingress configuration
+
+HTTPS ingress is controlled by the server [holmium](https://wiki.netz39.de/admin:servers:holmium) and forwarded to the configured servers.
+
+To set up a new HTTPS vhost, the following steps need to be taken:
+
+1. Select a domain (for internal services we use sub-domains of `.n39.eu`).
+2. Create an external CNAME from this domain to `dyndns.n39.eu`.
+3. Create an internal DNS entry in the [Descartes DNS config](https://gitea.n39.eu/Netz39_Admin/config.descartes/src/branch/prepare/dns_dhcp.txt). This is usually an alias on an existing server.
+4. Add the entry to the [holmium playbook](holmium.yml).
+5. Set up Dehydrated and vhost on the target host, e.g. using `setup-http-site-proxy`.
+
+Do not forget to execute all playbooks with relevant changes.
diff --git a/holmium.yml b/holmium.yml
index 392bb2f..753092b 100644
--- a/holmium.yml
+++ b/holmium.yml
@@ -6,7 +6,27 @@
     ansible_python_interpreter: /usr/bin/python3
 
   roles:
-
-  tasks:
-
-  handlers:
+  - role: nginx-https-ingress
+    vars:
+      ingress:
+      - server: kant
+        hosts:
+        - jabber.n39.eu
+        - conference.jabber.n39.eu
+        - spaceapi.n39.eu
+      - server: krypton
+        hosts:
+        - entities.svc.n39.eu
+      - server: pottwal
+        hosts:
+        - gitea.n39.eu
+        - uritools.n39.eu
+        - entities-validation.svc.n39.eu
+        - sl.n39.eu
+        - pad.n39.eu
+        - brotherql-web.n39.eu
+      - server: radon
+        hosts:
+        - nodered.n39.eu
+        - rabbitmq.n39.eu
+        - pwr-meter-pulse-gw-19i.svc.n39.eu
diff --git a/roles/nginx-https-ingress/files/apt-preference-99nginx b/roles/nginx-https-ingress/files/apt-preference-99nginx
new file mode 100644
index 0000000..1513083
--- /dev/null
+++ b/roles/nginx-https-ingress/files/apt-preference-99nginx
@@ -0,0 +1,4 @@
+Package: *
+Pin: origin nginx.org
+Pin: release o=nginx
+Pin-Priority: 900
diff --git a/roles/nginx-https-ingress/files/nginx.conf b/roles/nginx-https-ingress/files/nginx.conf
new file mode 100644
index 0000000..896c9a5
--- /dev/null
+++ b/roles/nginx-https-ingress/files/nginx.conf
@@ -0,0 +1,34 @@
+user  nginx;
+worker_processes  auto;
+
+error_log  /var/log/nginx/error.log notice;
+pid        /var/run/nginx.pid;
+
+
+events {
+  worker_connections  1024;
+}
+
+
+include /etc/nginx/passthrough.conf;
+
+http {
+  include       /etc/nginx/mime.types;
+  default_type  application/octet-stream;
+
+  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                    '$status $body_bytes_sent "$http_referer" '
+                    '"$http_user_agent" "$http_x_forwarded_for"';
+
+  access_log  /var/log/nginx/access.log  main;
+
+  sendfile        on;
+  #tcp_nopush     on;
+
+  keepalive_timeout  65;
+
+  #gzip  on;
+
+  include /etc/nginx/conf.d/*.conf;
+  include /etc/nginx/dehydrated-hosts/*;
+}
diff --git a/roles/nginx-https-ingress/handlers/main.yml b/roles/nginx-https-ingress/handlers/main.yml
new file mode 100644
index 0000000..53aebbb
--- /dev/null
+++ b/roles/nginx-https-ingress/handlers/main.yml
@@ -0,0 +1,7 @@
+# Handlers für nginx-https-proxy
+---
+- name: restart nginx
+  service:
+    name: nginx
+    state: restarted
+    enabled: yes
diff --git a/roles/nginx-https-ingress/tasks/main.yml b/roles/nginx-https-ingress/tasks/main.yml
new file mode 100644
index 0000000..ff095d1
--- /dev/null
+++ b/roles/nginx-https-ingress/tasks/main.yml
@@ -0,0 +1,89 @@
+# Tasks für nginx-https-proxy
+---
+### Install required packages
+#
+# At this point, we also check that apt is available,
+# which is assumed for all future operations.
+- name: Install nginx prerequisites
+  ansible.builtin.apt:
+    state: present
+    name:
+    - apt-transport-https
+    - ca-certificates
+    - gnupg2
+
+### Setup APT cache for the nginx repository
+#
+# We need the nginx repository to get the ngx_stream_core_module
+# for SSL passthrough.
+
+- name: Add nginx apt-key
+  apt_key:
+    url: https://nginx.org/keys/nginx_signing.key
+    state: present
+
+- name: Add nginx's APT repository
+  ansible.builtin.template:
+    src: templates/nginx.list.j2
+    dest: /etc/apt/sources.list.d/nginx.list
+  register: apt_repo
+
+- name: Set nginx APT preference
+  ansible.builtin.copy:
+    src: files/apt-preference-99nginx
+    dest: /etc/apt/preferences.d/99nginx
+
+- name: Update package cache
+  ansible.builtin.apt:
+    update_cache: true
+  when: apt_repo.changed
+
+### Install nginx
+
+- name: Install nginx
+  ansible.builtin.apt:
+    state: present
+    name:
+    # This version of nginx comes with the ngx_stream_core_module module
+    - nginx
+
+
+### Configuration
+- name: Setup passthrough matrix
+  ansible.builtin.template:
+    src: templates/passthrough.conf.j2
+    dest: /etc/nginx/passthrough.conf
+    owner: root
+    group: root
+    mode: '0644'
+  notify: restart nginx
+
+- name: Create directory for dehydrated forwardings
+  ansible.builtin.file:
+    path: /etc/nginx/dehydrated-hosts
+    state: directory
+    owner: root
+    group: root
+    mode: '0755'
+
+- name: Setup dehydrated forwardings
+  ansible.builtin.template:
+    src: templates/dehydrated-host.conf.j2
+    dest: "/etc/nginx/dehydrated-hosts/{{ item.server }}.conf"
+    owner: root
+    group: root
+    mode: '0644'
+  loop: "{{ ingress }}"
+  notify: restart nginx
+
+- name: Setup nginx configuration
+  # Note the order here: The nginx configuration _needs_ he dehydrated-hosts
+  # directory and the passthrough.conf file, so we do them first to ensure
+  # a valid configuration in case the playbook is cancelled mid-way.
+  ansible.builtin.copy:
+    src: files/nginx.conf
+    dest: /etc/nginx/nginx.conf
+    owner: root
+    group: root
+    mode: '0644'
+  notify: restart nginx
diff --git a/roles/nginx-https-ingress/templates/dehydrated-host.conf.j2 b/roles/nginx-https-ingress/templates/dehydrated-host.conf.j2
new file mode 100644
index 0000000..7647271
--- /dev/null
+++ b/roles/nginx-https-ingress/templates/dehydrated-host.conf.j2
@@ -0,0 +1,14 @@
+# Dehydrated forwardings for server {{ item.server }}
+{% if 'hosts' in item %}
+{%   for host in item.hosts %}
+server {
+  listen          80;
+  listen          [::]:80;
+  server_name     {{ host }};
+
+  location /.well-known/acme-challenge {
+    proxy_pass      http://{{ item.server }}.n39.eu:80;
+  }
+}
+{%   endfor %}
+{% endif %}
diff --git a/roles/nginx-https-ingress/templates/nginx.list.j2 b/roles/nginx-https-ingress/templates/nginx.list.j2
new file mode 100644
index 0000000..9e4235f
--- /dev/null
+++ b/roles/nginx-https-ingress/templates/nginx.list.j2
@@ -0,0 +1,2 @@
+deb https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
+deb-src https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
diff --git a/roles/nginx-https-ingress/templates/passthrough.conf.j2 b/roles/nginx-https-ingress/templates/passthrough.conf.j2
new file mode 100644
index 0000000..b5deb41
--- /dev/null
+++ b/roles/nginx-https-ingress/templates/passthrough.conf.j2
@@ -0,0 +1,25 @@
+# SSL passthrough matrix
+
+stream {
+  map $ssl_preread_server_name $name {
+{% for i in ingress %}
+{%   if 'hosts' in i %}
+{%     for host in i.hosts %}
+    {{ host }} {{ i.server }};
+{%     endfor %}
+{%   endif %}
+{% endfor %}
+  }
+
+{% for i in ingress %}
+  upstream {{ i.server }} {
+    server {{ i.server }}.n39.eu:443;
+  }
+{% endfor %}
+
+  server {
+    listen 443;
+    proxy_pass $name;
+    ssl_preread on;
+  }
+}