From 6fa0bfce32907d89417c2f08bc307b600d1b01f8 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sat, 17 Aug 2019 18:30:12 +0200 Subject: [PATCH 1/5] initial ansible implementation --- .gitignore | 1 + ansible/ansible.cfg | 5 ++++ ansible/inventory.example | 6 +++++ ansible/lemmy.yml | 54 +++++++++++++++++++++++++++++++++++++++ ansible/nginx.conf | 33 ++++++++++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 .gitignore create mode 100644 ansible/ansible.cfg create mode 100644 ansible/inventory.example create mode 100644 ansible/lemmy.yml create mode 100644 ansible/nginx.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6d0e0ba4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ansible/inventory diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 00000000..960a7c40 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +inventory=inventory + +[ssh_connection] +pipelining = True diff --git a/ansible/inventory.example b/ansible/inventory.example new file mode 100644 index 00000000..52b45d3c --- /dev/null +++ b/ansible/inventory.example @@ -0,0 +1,6 @@ +[lemmy] +# define the username and hostname that you use for ssh connection, and specify the domain +myuser@example.com domain=example.com letsencrypt_contact_email=your@email.com + +[all:vars] +ansible_connection=ssh diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml new file mode 100644 index 00000000..8830f203 --- /dev/null +++ b/ansible/lemmy.yml @@ -0,0 +1,54 @@ +--- +- hosts: all + + # Install python if required + # https://www.josharcher.uk/code/ansible-python-connection-failure-ubuntu-server-1604/ + gather_facts: False + pre_tasks: + - name: install python for Ansible + raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-setuptools) + args: + executable: /bin/bash + register: output + changed_when: output.stdout != "" + - setup: # gather facts + + tasks: + - name: install dependencies + apt: + pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot'] + + - name: create lemmy folder + file: path={{item.path}} state=directory + with_items: + - { path: '/lemmy/' } + + - name: add all template files + template: src={{item.src}} dest={{item.dest}} + with_items: + - { src: '../docker/prod/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } + - { src: 'nginx.conf', dest: '/lemmy/nginx.conf' } + + - name: request letsencrypt certificates + command: certbot certonly --standalone --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' + args: + creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' + + - name: enable and start docker service + systemd: + name: docker + enabled: yes + state: started + + - name: start docker-compose + docker_compose: + project_src: /peertube/ + state: present + pull: yes + + - name: renew certbot certificates + cron: + special_time=daily + name=certbot-renew + user=root + job="certbot certonly --webroot --webroot-path=/peertube/volumes/certbot/ -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'" diff --git a/ansible/nginx.conf b/ansible/nginx.conf new file mode 100644 index 00000000..e0aaec91 --- /dev/null +++ b/ansible/nginx.conf @@ -0,0 +1,33 @@ +server { + listen 80; + server_name {{ domain }}; + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name {{ domain }}; + + ssl_certificate /certs/live/{{ domain }}/fullchain.pem; + ssl_certificate_key /certs/live/{{ domain }}/privkey.pem; + + # TODO: add security params + + location / { + rewrite (\/(user|u|inbox|post|community|c|login|search|sponsors|communities|modlog|home)+) /static/index.html break; + proxy_pass http://0.0.0.0:8536; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} From c45ad217c4035cf1ce99c8544f9f83739e5b36d4 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sun, 18 Aug 2019 00:54:58 +0200 Subject: [PATCH 2/5] added env file --- .gitignore | 1 + ansible/lemmy.yml | 24 +++++++++++++++---- .../templates}/docker-compose.yml | 24 +++++++++---------- ansible/templates/env | 4 ++++ ansible/{ => templates}/nginx.conf | 0 5 files changed, 36 insertions(+), 17 deletions(-) rename {docker/prod => ansible/templates}/docker-compose.yml (52%) create mode 100644 ansible/templates/env rename ansible/{ => templates}/nginx.conf (100%) diff --git a/.gitignore b/.gitignore index 6d0e0ba4..2feec03c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ ansible/inventory +ansible/passwords/ diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml index 8830f203..144479a6 100644 --- a/ansible/lemmy.yml +++ b/ansible/lemmy.yml @@ -22,14 +22,28 @@ file: path={{item.path}} state=directory with_items: - { path: '/lemmy/' } + - { path: '/lemmy/volumes/' } + - { path: '/var/www/certbot' } - name: add all template files template: src={{item.src}} dest={{item.dest}} with_items: - - { src: '../docker/prod/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } - - { src: 'nginx.conf', dest: '/lemmy/nginx.conf' } + - { src: 'templates/env', dest: '/lemmy/.env' } + - { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } + - { src: 'templates/nginx.conf', dest: '/lemmy/nginx.conf' } + vars: + postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" + jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" - - name: request letsencrypt certificates + - name: set env file permissions + file: + path: "/lemmy/.env" + state: touch + mode: 0600 + access_time: preserve + modification_time: preserve + + - name: request initial letsencrypt certificate command: certbot certonly --standalone --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' args: creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' @@ -46,9 +60,9 @@ state: present pull: yes - - name: renew certbot certificates + - name: certbot renewal cronjob cron: special_time=daily name=certbot-renew user=root - job="certbot certonly --webroot --webroot-path=/peertube/volumes/certbot/ -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'" + job="certbot certonly --webroot --webroot-path=/var/www/certbot -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'" diff --git a/docker/prod/docker-compose.yml b/ansible/templates/docker-compose.yml similarity index 52% rename from docker/prod/docker-compose.yml rename to ansible/templates/docker-compose.yml index 271054fd..6904e6b2 100644 --- a/docker/prod/docker-compose.yml +++ b/ansible/templates/docker-compose.yml @@ -1,32 +1,32 @@ version: '2.4' services: + db: image: postgres:12-alpine restart: always environment: - POSTGRES_USER: rrr - POSTGRES_PASSWORD: rrr - POSTGRES_DB: rrr + POSTGRES_USER=lemmy + POSTGRES_PASSWORD=${DATABASE_PASSWORD} + POSTGRES_DB=lemmy volumes: - - db:/var/lib/postgresql/data + - ./volumes/db:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U rrr"] + test: ["CMD-SHELL", "pg_isready -U lemmy"] interval: 5s timeout: 5s retries: 20 + lemmy: image: dessalines/lemmy:v0.0.7.3 + restart: always ports: - "8536:8536" environment: - LEMMY_FRONT_END_DIR: /app/dist - DATABASE_URL: postgres://rrr:rrr@db:5432/rrr - JWT_SECRET: changeme - HOSTNAME: rrr - restart: always + LEMMY_FRONT_END_DIR=/app/dist + DATABASE_URL=postgres://rrr:rrr@db:5432/rrr + JWT_SECRET=${JWT_SECRET} + HOSTNAME=${DOMAIN} depends_on: db: condition: service_healthy -volumes: - db: diff --git a/ansible/templates/env b/ansible/templates/env new file mode 100644 index 00000000..e97aeef7 --- /dev/null +++ b/ansible/templates/env @@ -0,0 +1,4 @@ +DOMAIN={{ domain }} +DATABASE_PASSWORD={{ postgres_password }} +DATABASE_URL=postgres://lemmy:${DATABASE_PASSWORD}@db:5432/lemmy +JWT_SECRET={{ jwt_password }} diff --git a/ansible/nginx.conf b/ansible/templates/nginx.conf similarity index 100% rename from ansible/nginx.conf rename to ansible/templates/nginx.conf From cce330fa2df3b69b0648aa46fdd210a66536550e Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sun, 18 Aug 2019 00:59:55 +0200 Subject: [PATCH 3/5] add security headers to nginx (fixes #208) --- ansible/templates/nginx.conf | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/ansible/templates/nginx.conf b/ansible/templates/nginx.conf index e0aaec91..4fc8bc32 100644 --- a/ansible/templates/nginx.conf +++ b/ansible/templates/nginx.conf @@ -16,7 +16,35 @@ server { ssl_certificate /certs/live/{{ domain }}/fullchain.pem; ssl_certificate_key /certs/live/{{ domain }}/privkey.pem; - # TODO: add security params + # Various TLS hardening settings + # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + # Hide nginx version + server_tokens off; + + # Enable compression for JS/CSS/HTML bundle, for improved client load times. + # It might be nice to compress JSON, but leaving that out to protect against potential + # compression+encryption information leak attacks like BREACH. + gzip on; + gzip_types text/css application/javascript; + gzip_vary on; + + # Only connect to this site via HTTPS for the two years + add_header Strict-Transport-Security "max-age=63072000"; + + # Various content security headers + add_header Referrer-Policy "same-origin"; + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "DENY"; + add_header X-XSS-Protection "1; mode=block"; location / { rewrite (\/(user|u|inbox|post|community|c|login|search|sponsors|communities|modlog|home)+) /static/index.html break; From ac28ed6875f36ab0e3f0640cd725bab066959887 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Sun, 18 Aug 2019 16:39:19 +0200 Subject: [PATCH 4/5] various fixes --- ansible/lemmy.yml | 24 +++++++++++++----------- ansible/templates/docker-compose.yml | 19 +++++++++---------- ansible/templates/env | 2 +- ansible/templates/nginx.conf | 4 ++-- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml index 144479a6..7026200e 100644 --- a/ansible/lemmy.yml +++ b/ansible/lemmy.yml @@ -16,21 +16,25 @@ tasks: - name: install dependencies apt: - pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot'] + pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot', 'python-certbot-nginx'] + + - name: request initial letsencrypt certificate + command: certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' + args: + creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' - name: create lemmy folder file: path={{item.path}} state=directory with_items: - { path: '/lemmy/' } - { path: '/lemmy/volumes/' } - - { path: '/var/www/certbot' } - name: add all template files template: src={{item.src}} dest={{item.dest}} with_items: - { src: 'templates/env', dest: '/lemmy/.env' } - { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } - - { src: 'templates/nginx.conf', dest: '/lemmy/nginx.conf' } + - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf' } vars: postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" @@ -43,11 +47,6 @@ access_time: preserve modification_time: preserve - - name: request initial letsencrypt certificate - command: certbot certonly --standalone --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' - args: - creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' - - name: enable and start docker service systemd: name: docker @@ -56,13 +55,16 @@ - name: start docker-compose docker_compose: - project_src: /peertube/ + project_src: /lemmy/ state: present pull: yes + - name: reload nginx with new config + shell: nginx -s reload + - name: certbot renewal cronjob cron: special_time=daily - name=certbot-renew + name=certbot-renew-lemmy user=root - job="certbot certonly --webroot --webroot-path=/var/www/certbot -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'" + job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'" diff --git a/ansible/templates/docker-compose.yml b/ansible/templates/docker-compose.yml index 6904e6b2..af611045 100644 --- a/ansible/templates/docker-compose.yml +++ b/ansible/templates/docker-compose.yml @@ -1,4 +1,4 @@ -version: '2.4' +version: "3.3" services: @@ -6,9 +6,9 @@ services: image: postgres:12-alpine restart: always environment: - POSTGRES_USER=lemmy - POSTGRES_PASSWORD=${DATABASE_PASSWORD} - POSTGRES_DB=lemmy + - POSTGRES_USER=lemmy + - POSTGRES_PASSWORD=${DATABASE_PASSWORD} + - POSTGRES_DB=lemmy volumes: - ./volumes/db:/var/lib/postgresql/data healthcheck: @@ -23,10 +23,9 @@ services: ports: - "8536:8536" environment: - LEMMY_FRONT_END_DIR=/app/dist - DATABASE_URL=postgres://rrr:rrr@db:5432/rrr - JWT_SECRET=${JWT_SECRET} - HOSTNAME=${DOMAIN} + - LEMMY_FRONT_END_DIR=/app/dist + - DATABASE_URL=${DATABASE_URL} + - JWT_SECRET=${JWT_SECRET} + - HOSTNAME=${DOMAIN} depends_on: - db: - condition: service_healthy + - db diff --git a/ansible/templates/env b/ansible/templates/env index e97aeef7..12ff8506 100644 --- a/ansible/templates/env +++ b/ansible/templates/env @@ -1,4 +1,4 @@ DOMAIN={{ domain }} DATABASE_PASSWORD={{ postgres_password }} -DATABASE_URL=postgres://lemmy:${DATABASE_PASSWORD}@db:5432/lemmy +DATABASE_URL=postgres://lemmy:{{ postgres_password }}@db:5432/lemmy JWT_SECRET={{ jwt_password }} diff --git a/ansible/templates/nginx.conf b/ansible/templates/nginx.conf index 4fc8bc32..21560b5f 100644 --- a/ansible/templates/nginx.conf +++ b/ansible/templates/nginx.conf @@ -13,8 +13,8 @@ server { listen 443 ssl http2; server_name {{ domain }}; - ssl_certificate /certs/live/{{ domain }}/fullchain.pem; - ssl_certificate_key /certs/live/{{ domain }}/privkey.pem; + ssl_certificate /etc/letsencrypt/live/{{domain}}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{domain}}/privkey.pem; # Various TLS hardening settings # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html From 5e44ac207b35a8dbe883f055bdfccb50795cb9f4 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 20 Aug 2019 19:43:30 +0200 Subject: [PATCH 5/5] copy template files into docker/prod folder --- README.md | 22 ++++++- ansible/lemmy.yml | 2 +- .../prod}/docker-compose.yml | 4 +- docker/prod/env | 4 ++ docker/prod/nginx.conf | 61 +++++++++++++++++++ 5 files changed, 88 insertions(+), 5 deletions(-) rename {ansible/templates => docker/prod}/docker-compose.yml (91%) create mode 100644 docker/prod/env create mode 100644 docker/prod/nginx.conf diff --git a/README.md b/README.md index 593cf346..bb3cc144 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,32 @@ Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Infern ## Install -### Docker +### Ansible (recommended) -Make sure you have both docker and docker-compose(>=`1.24.0`) installed. +First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html), +eg using `sudo apt install ansible`, or the equivalent for you platform. + +Then run the following commands on your local computer: +```bash +git clone https://github.com/dessalines/lemmy.git +cd lemmy/ansible/ +cp inventory.example inventory +nano inventory # enter your server, domain, contact email +ansible-playbook lemmy.yml +``` + +### Manual + +Make sure you have both docker and docker-compose installed. ``` mkdir lemmy/ cd lemmy/ wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/env -O .env +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/nginx.conf +# you need to edit .env and nginx.conf to replace the indicated {{ variables }} +sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf docker-compose up -d ``` diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml index 7026200e..4ba80e90 100644 --- a/ansible/lemmy.yml +++ b/ansible/lemmy.yml @@ -33,7 +33,7 @@ template: src={{item.src}} dest={{item.dest}} with_items: - { src: 'templates/env', dest: '/lemmy/.env' } - - { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } + - { src: '../docker/prod/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf' } vars: postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" diff --git a/ansible/templates/docker-compose.yml b/docker/prod/docker-compose.yml similarity index 91% rename from ansible/templates/docker-compose.yml rename to docker/prod/docker-compose.yml index af611045..d55b2808 100644 --- a/ansible/templates/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 20 lemmy: - image: dessalines/lemmy:v0.0.7.3 + image: dessalines/lemmy:v0.0.7 .3 restart: always ports: - "8536:8536" @@ -27,5 +27,5 @@ services: - DATABASE_URL=${DATABASE_URL} - JWT_SECRET=${JWT_SECRET} - HOSTNAME=${DOMAIN} - depends_on: + depends_on: - db diff --git a/docker/prod/env b/docker/prod/env new file mode 100644 index 00000000..06f3cfe2 --- /dev/null +++ b/docker/prod/env @@ -0,0 +1,4 @@ +DOMAIN={{your domain}} +DATABASE_PASSWORD={{a random password for postgres}} +DATABASE_URL=postgres://lemmy:{{ the same postgres password again }}@db:5432/lemmy +JWT_SECRET={{ a random password for jwt}} diff --git a/docker/prod/nginx.conf b/docker/prod/nginx.conf new file mode 100644 index 00000000..918851a0 --- /dev/null +++ b/docker/prod/nginx.conf @@ -0,0 +1,61 @@ +server { + listen 80; + server_name {{ your domain }}; + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name {{ your domain }}; + + ssl_certificate /etc/letsencrypt/live/{{ your domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ your domain }}/privkey.pem; + + # Various TLS hardening settings + # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + # Hide nginx version + server_tokens off; + + # Enable compression for JS/CSS/HTML bundle, for improved client load times. + # It might be nice to compress JSON, but leaving that out to protect against potential + # compression+encryption information leak attacks like BREACH. + gzip on; + gzip_types text/css application/javascript; + gzip_vary on; + + # Only connect to this site via HTTPS for the two years + add_header Strict-Transport-Security "max-age=63072000"; + + # Various content security headers + add_header Referrer-Policy "same-origin"; + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "DENY"; + add_header X-XSS-Protection "1; mode=block"; + + location / { + rewrite (\/(user|u|inbox|post|community|c|login|search|sponsors|communities|modlog|home)+) /static/index.html break; + proxy_pass http://0.0.0.0:8536; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +}