diff --git a/group_vars/nginx_rtmp.yml b/group_vars/nginx_rtmp.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d5626daa0dca8487e3fa234c57ca8db1076db5f8
--- /dev/null
+++ b/group_vars/nginx_rtmp.yml
@@ -0,0 +1,4 @@
+---
+
+glob_nginx_rtmp:
+  uri: stream.crans.org
diff --git a/group_vars/reverseproxy.yml b/group_vars/reverseproxy.yml
index cd01d6a323b9537d0bfe0c84003b2a088d386bdd..fa185203f22c240f5ba2f4d8a7236364c3cce3fc 100644
--- a/group_vars/reverseproxy.yml
+++ b/group_vars/reverseproxy.yml
@@ -48,6 +48,7 @@ nginx:
     #    - {from: grafana.crans.org, to: "10.231.136.102:3000"}
     #    - {from: webirc.crans.org, to: "10.231.136.1:9000"}
     - {from: framadate.crans.org, to: 172.16.10.109}
+    - {from: stream.crans.org, to: 172.16.10.118}
     #    - {from: mailman.crans.org, to: 10.231.136.180}
     #
     #    # Zamok
diff --git a/hosts b/hosts
index 22b567d8b4822a61c56d0bbdd78858ee602b4c11..b6c94842630b9bb96ef623050b7c4b79d4dbb828 100644
--- a/hosts
+++ b/hosts
@@ -25,6 +25,9 @@
 # [test_vm]
 # re2o-test.adm.crans.org
 
+[nginx_rtmp]
+fluxx.adm.crans.org
+
 [reverseproxy]
 hodaur.adm.crans.org
 frontdaur.adm.crans.org
@@ -81,6 +84,7 @@ gitlab-ci.adm.crans.org
 hodaur.adm.crans.org
 monitoring.adm.crans.org
 boeing.adm.crans.org
+fluxx.adm.crans.org
 
 [ovh_physical]
 sputnik.adm.crans.org
diff --git a/plays/nginx_rtmp.yml b/plays/nginx_rtmp.yml
new file mode 100755
index 0000000000000000000000000000000000000000..b515bb231fb5122a3b8779d5ddfaa2923be40087
--- /dev/null
+++ b/plays/nginx_rtmp.yml
@@ -0,0 +1,7 @@
+#!/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/roles/nginx-rtmp/files/index.html b/roles/nginx-rtmp/files/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..3f0323cc83bc1998c5d66bcc2e03588f9e59dfc3
--- /dev/null
+++ b/roles/nginx-rtmp/files/index.html
@@ -0,0 +1,129 @@
+<!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 h-100 p-3">
+      <div class="row align-items-center h-100">
+        <div class="col-12">
+          <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="linkStream">Ouvrir le flux dans une application externe</a>
+            —
+            <a class="text-white text-decoration-none" href="#aboutModal" data-toggle="modal">À propos</a>
+          </p>
+        </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>rtmp://stream.adm.crans.org/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
+              rtmp://stream.adm.crans.org/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
+
+  // If stream exists, then load
+  const player = videojs('my-video', {
+    controls: true,
+    autoplay: true,
+    preload: 'auto',
+    muted: true,
+    fluid: true,
+    aspectRatio: '16:9'
+  })
+
+  fetch(streamUrl, {
+    method: 'HEAD',
+    cache: 'no-cache'
+  }).then((response) => {
+    if (response.ok) {
+      // Stream exists, load!
+      player.src({
+        type: 'application/x-mpegURL',
+        src: streamUrl
+      });
+    } else {
+      // Stream does not exist, alert!
+      player.poster('/no-stream.jpg')
+    }
+  });
+  </script>
+</body>
+</html>
diff --git a/roles/nginx-rtmp/files/no-stream.jpg b/roles/nginx-rtmp/files/no-stream.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..af44c5a43ea0399422d9fe21c302a262c53f7a3e
Binary files /dev/null and b/roles/nginx-rtmp/files/no-stream.jpg differ
diff --git a/roles/nginx-rtmp/handlers/main.yml b/roles/nginx-rtmp/handlers/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3b8f2d6927c32e1ccdb51d04f84b0944f2aa7d93
--- /dev/null
+++ b/roles/nginx-rtmp/handlers/main.yml
@@ -0,0 +1,7 @@
+---
+
+- 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
new file mode 100644
index 0000000000000000000000000000000000000000..c1e61bfdc96a081d21db285bda84327a94b18d65
--- /dev/null
+++ b/roles/nginx-rtmp/tasks/main.yml
@@ -0,0 +1,54 @@
+---
+
+- 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
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
new file mode 100644
index 0000000000000000000000000000000000000000..e780fb6c9a9842a58339015195642b734fad73d4
--- /dev/null
+++ b/roles/nginx-rtmp/templates/nginx/modules-available/60-rtmp.conf.j2
@@ -0,0 +1,18 @@
+{{ 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
new file mode 100644
index 0000000000000000000000000000000000000000..2a074c8da1ec704eaf1a416aaccf9cc1f0f3ed19
--- /dev/null
+++ b/roles/nginx-rtmp/templates/nginx/sites-available/stream.j2
@@ -0,0 +1,40 @@
+{{ 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;
+        }
+    }
+}
+