Reverse proxy
The web UI and REST API of MicroB2B speak plain HTTP. You always put a reverse proxy in front of them, such as Caddy or nginx, to handle HTTPS. This page explains what the proxy must do and gives a working example for each.
Why you need one
- The proxy holds the TLS certificate and renews it.
- Sign-in cookies only work over HTTPS, at one stable address: your
PUBLIC_URL. - It is the natural place for a load balancer or your company’s ingress.
An AS2 receiver can sit behind the same proxy, or serve HTTPS itself. See HTTPS on the receiver below.
The rules
- Serve MicroB2B at the root of a host name, such as
https://b2b.example.com/. It cannot run under a path such as/b2b. Its cookies only work at the root. - Set
PUBLIC_URLto the address users type, such ashttps://b2b.example.com. MicroB2B builds links fromPUBLIC_URLonly. It ignores theHost,X-Forwarded-ProtoandX-Forwarded-Hostheaders. - Keep the plain HTTP ports private. The proxy reaches MicroB2B over loopback or a network that only the proxy and MicroB2B share. A receiver behind a proxy on the same host binds to
127.0.0.1. - Pass
Cookie,Set-CookieandLocationunchanged. - Do not cache
/ui/login,/ui/setup/*or/ui/auth/*. - Do not buffer live pages. The message list, message page, dashboard and Logs page receive live updates. The proxy must not buffer these responses. It must keep a quiet connection open for more than 20 seconds. MicroB2B sends a small keep-alive every 20 seconds.
- Keep setup links out of your proxy’s access log, or keep that log private. A setup link lets someone choose a user’s password. MicroB2B leaves these paths out of its own log.
- Set
Strict-Transport-Securityat the proxy. MicroB2B does not set it, because it does not handle TLS itself.
/health is open to anyone. Everything else needs a signed-in user or an API token.
What to set
| Web UI and REST API | AS2 receiver | |
|---|---|---|
| Upstream | http://127.0.0.1:8080 (HTTP_PORT) | http://127.0.0.1:8081 (the receiver’s address and port) |
| Largest request | 66 MiB. A message you send by hand can be 64 MiB | 1 MiB above the largest message size you allow (default 128 MiB) |
| Buffering | Off for responses | Either way |
| Timeouts | Keep a quiet connection open for an hour or more | See below |
| HTTP version | Any | HTTP/1.1 |
Why 1 MiB above the message size limit? Then MicroB2B, not the proxy, refuses a message that is too large. MicroB2B answers with a proper refusal, and a receipt (MDN) when the partner asked for one. The proxy would only send a bare error.
Receiver timeouts. A partner may pause up to 60 seconds between parts of a message. The whole message must arrive within 60 seconds plus 1 second per 32 KiB. For a 128 MiB message, that is about 69 minutes. Allow at least that. The answer to a large signed and encrypted message can take minutes, when the partner waits for the receipt on the same connection.
Caddy
Caddy gets and renews certificates by itself, for the names in the file. Those names must point to the Caddy server, and ports 80 and 443 must be open.
b2b.example.com {
request_body {
max_size 66MiB
}
header Strict-Transport-Security "max-age=31536000"
# Caddy streams text/event-stream responses unbuffered by itself.
reverse_proxy 127.0.0.1:8080
}
as2.example.com {
request_body {
max_size 129MiB
}
reverse_proxy /as2* 127.0.0.1:8081 {
transport http {
response_header_timeout 10m
}
}
}Caddy passes a message on as it arrives, so MicroB2B’s own time limits apply to the partner. /as2* covers the receiver’s path and a receipt callback path below it, such as /as2/mdn. Proxy every path a receiver serves.
nginx
This example uses certificates you get elsewhere, for example from certbot or your company’s CA.
server {
listen 443 ssl;
server_name b2b.example.com;
ssl_certificate /etc/ssl/b2b.example.com.crt;
ssl_certificate_key /etc/ssl/b2b.example.com.key;
add_header Strict-Transport-Security "max-age=31536000" always;
client_max_body_size 66m;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Event streams: unbuffered, open for an hour.
proxy_buffering off;
proxy_read_timeout 1h;
}
}
server {
listen 443 ssl;
server_name as2.example.com;
ssl_certificate /etc/ssl/as2.example.com.crt;
ssl_certificate_key /etc/ssl/as2.example.com.key;
client_max_body_size 129m;
# A pause between two reads of the body, as MicroB2B allows it.
client_body_timeout 60s;
location /as2 {
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 10m;
proxy_send_timeout 10m;
}
}nginx reads the whole message before it passes it on. That keeps a slow partner away from MicroB2B. client_body_timeout is then the pause a partner is allowed. location /as2 also covers a receipt callback path below the receiver’s path.
Things that change behind a proxy
- Partner addresses. A message records the proxy’s address as where it came from. The partner’s own address is in the proxy’s access log. That is why the examples set
X-Forwarded-For. - Sign-in throttling. MicroB2B slows down repeated failed sign-ins per client address. Behind a proxy, every client has the proxy’s address, so they share that limit.
- Identity providers. Each provider has the sign-in address registered that MicroB2B builds from
PUBLIC_URL. When you changePUBLIC_URL, update it at the provider too. Access > Identity providers shows the address to register. - Several MicroB2B servers behind one address need the same
SESSION_KEY. - Partner client certificates. MicroB2B cannot check a partner’s TLS client certificate behind a proxy. TLS ends at the proxy, and MicroB2B trusts no header about it.
HTTPS on the receiver
An AS2 receiver can serve HTTPS itself, with a certificate you upload to MicroB2B. Then you need no proxy in front of it. That suits a small installation on one server. It can serve HTTP and HTTPS side by side, which helps while partners move to HTTPS.
The receiver has no automatic certificate renewal. Before a certificate expires, you upload the new one, choose it on the receiver and restart the receiver. If you want automatic renewal, put Caddy in front instead.
See AS2 receiver for the settings.
Browsers on other sites
By default, the REST API only answers browser requests from MicroB2B’s own pages. Tools such as curl and scripts are not affected. To let a web application on another site call the API from the browser, set ALLOWED_ORIGINS:
| Value | Meaning |
|---|---|
| empty (default) | Only MicroB2B’s own pages |
https://app.example.com,https://other.example.com | These sites too |
* | Every site. MicroB2B logs a warning |
Security headers
MicroB2B sends these headers with every answer: Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff and Referrer-Policy: same-origin. You do not need to add them at the proxy.
Every form in the web UI carries a protection token against cross-site requests. A form you submit after MicroB2B restarted, or after two idle hours, is refused with CSRF token invalid. Reload the page and try again.