Ever dreamt of customising your nginx error pages?

nginx 404 error message

A file dump.

nginx_errors.sed
# patch for src/http/ngx_http_special_response.c error HTML strings
# (run with `sed -n`)

# Original source¹ goes like:

# L1  static char ngx_http_error_404_page[] =
# L2  "<html>" CRLF
# L3  "<head><title>404 Not Found</title></head>" CRLF
# L4  "<body>" CRLF
# L5  "<center><h1>404 Not Found</h1></center>" CRLF
#     ;
#
# (lines 60-337)

40,360 {
  N; # Append the next line to the pattern space for look-ahead

  # Little whitespace at the top [L4]
  s|<body>"|<body style=\\"padding-top: 8rem\\">"|;
  # Change error message font to mono-space [L5]
  s|<h1>([^<]+)</h1>|<h1><tt>\1</tt></h1>|;

  # Redish background for HTTP status >= 400
  # Change current line [L4] if next line matches [L5] - both after previous patching
  /<h1><tt>[^123]/ {
    # Tailwind red-300
    s|rem\\"|rem; background: oklch(80.8% 0.114 19.571)\\"|;
  }

  # British l10n [L3] [L5]
  s|Authorization|Authorisation|;

  P; # Print the first line of the two-line pattern space
  D; # Delete the first line and restart cycle with the second line
}

#     static u_char ngx_http_error_tail[] =
#     "<hr><center>nginx</center>" CRLF
#     "</body>" CRLF
#     "</html>" CRLF
#     ;
#
# (lines 21-39)

1,60 {
  # Remove "nginx" branding from error pages
  /<hr><center>/d;
}

p;

# ¹ https://github.com/nginx/nginx/blob/master/src/http/ngx_http_special_response.c

Nix makes it easy to apply it to the upstream package automatically on every update:

nixpkgs.overlays = [
  (_final: prev: {
    nginxStable = prev.nginxStable.overrideAttrs (oldAttrs: {
      postPatch =
        (oldAttrs.postPatch or "")
        + ''
          sed -i -nEf ${./www/nginx_errors.sed} src/http/ngx_http_special_response.c
        '';
    });
  })
];

Thinking about it, let’s rather say simple instead of easy in the context of Nix.

FWIW, the world needed a (certainly bug-laden) TextMate grammar for sed (using it for Shiki here):

sed.json
{
  "displayName": "sed",
  "name": "sed",
  "scopeName": "source.sed",
  "patterns": [
    { "include": "#comment" },
    { "include": "#instruction" }
  ],
  "repository": {
    "comment": {
      "match": "#.*$",
      "name": "comment.line.number-sign.sed"
    },
    "instruction": {
      "begin": "(^\\s*)((?=[0-9$/]))?",
      "end": "$",
      "beginCaptures": {
        "1": { "name": "leading-whitespace" }
      },
      "patterns": [
        { "include": "#comment" },
        {
          "begin": "(?=[0-9$/])",
          "end": "(?=\\s*!?\\s*([a-zA-Z{]|$))",
          "name": "meta.address-range.sed",
          "patterns": [
            { "include": "#address_single" },
            { "match": ",", "name": "punctuation.separator.sed" }
          ]
        },
        { "include": "#command" }
      ]
    },
    "address_single": {
      "patterns": [
        {
          "match": "\\d+",
          "name": "constant.numeric.address.sed"
        },
        {
          "match": "\\$",
          "name": "variable.language.sed"
        },
        {
          "match": "(/)((?:\\\\/|[^/])*)(/)",
          "captures": {
            "1": { "name": "punctuation.definition.separator.sed" },
            "2": {
              "name": "string.regexp.sed",
              "patterns": [
                { "include": "#escaped_char" },
                { "include": "#char_class" }
              ]
            },
            "3": { "name": "punctuation.definition.separator.sed" }
          }
        }
      ]
    },
    "command": {
      "patterns": [
        { "include": "#substitution" },
        { "include": "#command_block" },
        {
          "match": "(a|b|c|d|D|g|G|h|H|i|l|n|N|p|P|q|Q|r|t|T|w|W|x|y|z|=|!|:)",
          "name": "support.function.sed"
        }
      ]
    },
    "command_block": {
      "begin": "\\s*({)",
      "end": "}",
      "beginCaptures": {
        "1": { "name": "punctuation.section.block.begin.sed" }
      },
      "endCaptures": {
        "0": { "name": "punctuation.section.block.end.sed" }
      },
      "name": "meta.block.sed",
      "patterns": [
        { "include": "#comment" },
        { "include": "#instruction" },
        { "match": ";", "name": "punctuation.separator.sed" }
      ]
    },
    "substitution": {
      "name": "meta.substitution.sed",
      "match": "(s)([^\\w\\s])((?:\\\\.|[^\\\\])*?)\\2((?:\\\\.|[^\\\\])*?)\\2([giIpPw]*)",
      "captures": {
        "1": { "name": "support.function.sed" },
        "2": { "name": "punctuation.definition.separator.sed" },
        "3": {
          "name": "string.regexp.sed",
          "patterns": [
            { "include": "#escaped_char" },
            { "include": "#char_class" }
          ]
        },
        "4": {
          "name": "string.replacement.sed",
          "patterns": [
            { "include": "#escaped_char" }
          ]
        },
        "5": { "name": "keyword.other.option.sed" }
      }
    },
    "escaped_char": {
      "match": "\\\\.",
      "name": "constant.character.escape.sed"
    },
    "char_class": {
      "begin": "\\[",
      "beginCaptures": { "0": { "name": "punctuation.definition.character-class.begin.sed" } },
      "end": "(?<!:)\\]",
      "endCaptures": { "0": { "name": "punctuation.definition.character-class.end.sed" } },
      "name": "constant.other.character-class.set.sed",
      "patterns": [
        {
          "match": "\\^",
          "name": "keyword.operator.negation.sed"
        },
        {
          "match": "\\[:[a-zA-Z]+:\\]",
          "name": "constant.other.posix-class.sed"
        },
        { "include": "#escaped_char" }
      ]
    }
  }
}