Skip to content

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_URL to the address users type, such as https://b2b.example.com. MicroB2B builds links from PUBLIC_URL only. It ignores the Host, X-Forwarded-Proto and X-Forwarded-Host headers.
  • 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-Cookie and Location unchanged.
  • 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-Security at 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 APIAS2 receiver
Upstreamhttp://127.0.0.1:8080 (HTTP_PORT)http://127.0.0.1:8081 (the receiver’s address and port)
Largest request66 MiB. A message you send by hand can be 64 MiB1 MiB above the largest message size you allow (default 128 MiB)
BufferingOff for responsesEither way
TimeoutsKeep a quiet connection open for an hour or moreSee below
HTTP versionAnyHTTP/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 change PUBLIC_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:

ValueMeaning
empty (default)Only MicroB2B’s own pages
https://app.example.com,https://other.example.comThese 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.