Nginx Notion Proxy

What these configs do:

  • Proxy all traffic from Notion to your custom domain notion.example.tld and deliver it to your clients
  • WebSocket proxy support
  • Image local disk caching support
  • Correct URL rewriting
  • Get access logs from real request IPs

This post is a proof of concept proxying a general XaaS without using any vendor-locked FaaS such as Cloudflare Workers or AWS Lambda.

Main origin Nginx config:

# Notion Proxy
server {
  listen                  80;
  listen                  [::]:80;

  server_name             notion.example.tld notion-origin.example.tld;
  return                  301 https://$server_name$request_uri;
}

server {
  listen                  443       ssl http2;
  listen                  [::]:443  ssl http2;

  server_name             notion.example.tld notion-origin.example.tld;

  # Certificate
  ssl_certificate         /root/.acme.sh/example.tld_ecc/fullchain.cer;
  ssl_certificate_key     /root/.acme.sh/example.tld_ecc/example.tld.key;

  ssl_stapling            on;
  ssl_stapling_verify     on;

  # Longer resolver timeout
  # https://stackoverflow.com/a/26833369/412385
  resolver_timeout        30s;

  # Hide unwanted headers
  proxy_hide_header       Content-Security-Policy;
  proxy_hide_header       Expect-CT;

  location / {
    proxy_pass https://www.notion.so;
    proxy_set_header Referer https://www.notion.so/;

    # prevents 502 bad gateway error
    proxy_buffers 8 32k;
    proxy_buffer_size 64k;

    proxy_set_header Accept-Encoding "";
    sub_filter 'www.notion.so' 'notion.example.tld';
    sub_filter_last_modified on;
    sub_filter_types *;
    sub_filter_once off;

    # Replace cookie domain
    proxy_cookie_domain www.notion.so notion.example.tld;

    # Rewrite common redirects
    proxy_redirect ~*https??://www.notion.so(.*)$ https://notion.example.tld$1;

    # Caching images
    set $use_proxy_cache off;

    if ($uri ~* ^/image) {
      set $use_proxy_cache  main_disk;
    }

    proxy_cache             $use_proxy_cache;
    proxy_cache_valid       200 304  1M;
    proxy_cache_valid       301 302  15m;
    proxy_cache_valid       any      1m;

    break;
  }
}

server {
  listen                  80;
  listen                  [::]:80;

  server_name             msgstore.notion.example.tld msgstore.notion-origin.example.tld;
  return                  301 https://$server_name$request_uri;
}

server {
  listen                  443       ssl http2;
  listen                  [::]:443  ssl http2;

  server_name             msgstore.notion.example.tld msgstore.notion-origin.example.tld;

  # Certificate
  ssl_certificate         /root/.acme.sh/example.tld_ecc/fullchain.cer;
  ssl_certificate_key     /root/.acme.sh/example.tld_ecc/example.tld.key;

  ssl_stapling            on;
  ssl_stapling_verify     on;

  # Longer resolver timeout
  # https://stackoverflow.com/a/26833369/412385
  resolver_timeout        30s;

  location / {
    proxy_pass https://msgstore.www.notion.so;
    proxy_set_header Referer https://www.notion.so/;

    # prevents 502 bad gateway error
    proxy_buffers 8 32k;
    proxy_buffer_size 64k;

    # Replace cookie domain
    proxy_cookie_domain www.notion.so msgstore.notion.example.tld;

    # enables WS support
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_cache             off;

    break;
  }
}

Proxy CDN node:

server {
  listen                  80;
  listen                  [::]:80;

  server_name             notion.example.tld;
  return                  301 https://$host$request_uri;
}

server {
  listen                  443       ssl http2;
  listen                  [::]:443  ssl http2;

  server_name             notion.example.tld;

  # Certificate
  ssl_certificate         /root/.acme.sh/example.tld_ecc/fullchain.cer;
  ssl_certificate_key     /root/.acme.sh/example.tld_ecc/example.tld.key;

  ssl_stapling            on;
  ssl_stapling_verify     on;

  # Global proxy settings
  proxy_hide_header       Vary;

  # Add location header
  add_header              X-EbS-Edge-Cache $upstream_cache_status;

  # Global cache size increase
  proxy_buffers 8 32k;
  proxy_buffer_size 64k;

  location / {
    proxy_pass https://notion-origin.example.tld;
    proxy_buffering on;

    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

    # Enable the following if `proxy_pass` to a https protocol
    proxy_set_header        X-Forwarded-Proto https;

    # Caching images
    set $use_proxy_cache off;

    if ($uri ~* ^/image) {
      set $use_proxy_cache  main_disk;
    }

    proxy_cache             $use_proxy_cache;
  }
}

server {
  listen                  80;
  listen                  [::]:80;

  server_name             msgstore.notion.example.tld;
  return                  301 https://$host$request_uri;
}

server {
  listen                  443       ssl http2;
  listen                  [::]:443  ssl http2;

  server_name             msgstore.notion.example.tld;

  # Certificate
  ssl_certificate         /root/.acme.sh/example.tld_ecc/fullchain.cer;
  ssl_certificate_key     /root/.acme.sh/example.tld_ecc/example.tld.key;

  ssl_stapling            on;
  ssl_stapling_verify     on;

  # Global proxy settings
  proxy_hide_header       Vary;

  # Add location header
  add_header              X-EbS-Edge-Cache $upstream_cache_status;

  # Global cache size increase
  proxy_buffers 8 32k;
  proxy_buffer_size 64k;

  location / {
    proxy_pass https://msgstore.notion-origin.example.tld;
    proxy_buffering on;

    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

    # enables WS support
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_cache             off;
  }
}

The CDN node acts as a external reverse proxy that can proxy traffic and deliver it to the nearest users.