# SPDX-FileCopyrightText: 2026 Progmasoft # SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 # Only a WebSocket upgrade reaches the applications. Forwarding any Upgrade # header with "Connection: upgrade" on every request would let a client ask an # upstream for another protocol (h2c) behind Nginx. map $http_upgrade $progmaweb_upgrade { default ""; ~*^websocket$ websocket; } map $progmaweb_upgrade $progmaweb_connection { default ""; websocket upgrade; } server { listen 80; listen [::]:80; server_name progmasoft.com www.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://progmasoft.com$request_uri; } } server { listen 80; listen [::]:80; server_name account.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://account.progmasoft.com$request_uri; } } server { listen 80; listen [::]:80; server_name viget.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://viget.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name www.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; return 308 https://progmasoft.com$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 1m; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Upgrade $progmaweb_upgrade; proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name account.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 1m; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; # Account, authentication, recovery, dashboard, and API responses are not # public search documents. The header also covers non-HTML responses. add_header X-Robots-Tag "noindex, nofollow, noarchive" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Upgrade $progmaweb_upgrade; proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name viget.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 1m; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; location = /login { return 308 https://account.progmasoft.com/login$is_args$args; } location = /register { return 308 https://account.progmasoft.com/register$is_args$args; } location = /recover { return 308 https://account.progmasoft.com/recover$is_args$args; } location = /api/v1/status { proxy_http_version 1.1; proxy_pass http://127.0.0.1:5080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Real-IP $remote_addr; } # The index of a Tazel plugin, which Tazel reads to choose a version and # check the JAR it downloads: from the API, under its own path. location ~ "^/dslplugins/tazel/([A-Za-z_][A-Za-z0-9_]*)/([A-Za-z_][A-Za-z0-9_]*)/index\.json$" { proxy_http_version 1.1; proxy_pass http://127.0.0.1:5085/api/v1/viget/dslplugins/tazel/$1/$2/index.json; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Real-IP $remote_addr; add_header Cache-Control "no-cache" always; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; } # The JARs of Tazel plugins, from the files of ViGet. A version is never # replaced, so a JAR is cached for good. location ~ "^/dslplugins/tazel/[A-Za-z_][A-Za-z0-9_]*/[A-Za-z_][A-Za-z0-9_]*/[0-9A-Za-z.+-]+/[A-Za-z_][A-Za-z0-9_]*-[0-9A-Za-z.+-]+\.jar$" { root /srv/viget/files; default_type application/java-archive; types { } add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; } location ^~ /api/ { return 404; } location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Upgrade $progmaweb_upgrade; proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } # Progmasoft OAuth: the OAuth 2.1 and OpenID Connect provider of Progmasoft # accounts. credentials.progmasoft.com is the issuer: the discovery documents, # the key set and the endpoints clients call directly go to the account API; # its home page is a page of the Next.js site. server { listen 80; listen [::]:80; server_name credentials.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://credentials.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name credentials.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 64k; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header X-Robots-Tag "noindex, nofollow, noarchive" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; # The protocol endpoints, the discovery documents and the JSON endpoints of # the pages belong to the account API; it answers only OAuth paths here. location ~ ^/(connect|\.well-known|api)/ { proxy_pass http://127.0.0.1:5085; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_read_timeout 30s; } # The home page of the issuer is a page of the Progmasoft site. location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Upgrade $progmaweb_upgrade; proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } # authorize.progmasoft.com: the pages people see in Progmasoft OAuth. The # authorization and end session endpoints and the JSON paths of the pages go to # the account API; sign-in, the consent screen and the allowed applications are # pages of the Next.js site. server { listen 80; listen [::]:80; server_name authorize.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://authorize.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name authorize.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 64k; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header X-Robots-Tag "noindex, nofollow, noarchive" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; # The authorization and end session endpoints and the JSON endpoints of the # pages belong to the account API; it answers only those here. location ~ ^/(connect|api)/ { proxy_pass http://127.0.0.1:5085; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_read_timeout 30s; } # Sign-in, the consent screen and the allowed applications are pages of the # Progmasoft site. location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Upgrade $progmaweb_upgrade; proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } # The Progmasoft Console: developers make and manage the tokens of their # applications here, signed in with their Progmasoft account session. The JSON # paths go to the account API; the pages are pages of the Next.js site. server { listen 80; listen [::]:80; server_name console.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://console.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name console.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 64k; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header X-Robots-Tag "noindex, nofollow, noarchive" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; location ^~ /api/ { proxy_pass http://127.0.0.1:5085; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_read_timeout 30s; } location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header Upgrade $progmaweb_upgrade; proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } # The product catalog (product.progmasoft.com) and the product site Progmaweb # serves itself (catch3.progmasoft.com): pages of the Next.js site only. server { listen 80; listen [::]:80; server_name product.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://product.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name product.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 1m; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_read_timeout 60s; } } server { listen 80; listen [::]:80; server_name catch3.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://catch3.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name catch3.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 1m; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; location / { proxy_pass http://127.0.0.1:3010; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_read_timeout 60s; } } # The Maven repository of Progmasoft: /tazel holds com.progmasoft.tazel:tazel-api, # which plugins of Tazel compile against. Static files only, copied from the # Gradle staging repository of a release; see ops/README.md. server { listen 80; listen [::]:80; server_name maven.progmasoft.com; location ^~ /.well-known/acme-challenge/ { root /var/lib/letsencrypt; default_type text/plain; } location / { return 308 https://maven.progmasoft.com$request_uri; } } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name maven.progmasoft.com; ssl_certificate /etc/letsencrypt/live/progmasoft.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/progmasoft.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; root /srv/maven; autoindex on; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Robots-Tag "noindex" always; location = / { return 302 https://maven.progmasoft.com/tazel/; } location ^~ /tazel/ { try_files $uri $uri/ =404; } location / { return 404; } }