{"id":45295,"date":"2022-02-23T02:45:51","date_gmt":"2022-02-22T17:45:51","guid":{"rendered":"https:\/\/jirak.net\/wp\/avoiding-the-top-10-nginx-configuration-mistakes\/"},"modified":"2022-02-23T03:34:11","modified_gmt":"2022-02-22T18:34:11","slug":"avoiding-the-top-10-nginx-configuration-mistakes","status":"publish","type":"post","link":"https:\/\/jirak.net\/wp\/avoiding-the-top-10-nginx-configuration-mistakes\/","title":{"rendered":"Avoiding the Top 10 NGINX Configuration Mistakes"},"content":{"rendered":"<p>Avoiding the Top 10 NGINX Configuration Mistakes<\/p>\n<p>When we help NGINX users who are having problems, we often see the same configuration mistakes we\u2019ve seen over and over in other users\u2019 configurations&nbsp;&ndash; sometimes even in configurations written by fellow NGINX engineers! In this blog we look at&nbsp;10 of the most common errors, explaining what\u2019s wrong and how to fix it.<\/p>\n<ol>\n<li><a href=\"#insufficient-fds\">Not enough file descriptors per worker<\/a><\/li>\n<li><a href=\"#error_log-off\">The <code>error_log<\/code> <code>off<\/code> directive<\/a><\/li>\n<li><a href=\"#no-keepalives\">Not enabling keepalive connections to upstream servers<\/a><\/li>\n<li><a href=\"#directive-inheritance\">Forgetting how directive inheritance works<\/a><\/li>\n<li><a href=\"#proxy_buffering-off\">The <code>proxy_buffering<\/code> <code>off<\/code> directive<\/a><\/li>\n<li><a href=\"#if\">Improper use of the <code>if<\/code> directive<\/a><\/li>\n<li><a href=\"#health-checks\">Excessive health checks<\/a><\/li>\n<li><a href=\"#unsecured-metrics\">Unsecured access to metrics<\/a><\/li>\n<li><a href=\"#ip_hash\">Using <code>ip_hash<\/code> when all traffic comes from the same \/24 CIDR block<\/a><\/li>\n<li><a href=\"#upstream-groups\">Not taking advantage of upstream groups<\/a><\/li>\n<\/ol>\n<h2 id=\"insufficient-fds\">Mistake 1: Not Enough File Descriptors per Worker<\/h2>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/ngx_core_module.html#worker_connections\" rel=\"noopener noreferrer\"><code>worker_connections<\/code><\/a> directive sets the maximum number of simultaneous connections that a NGINX worker process can have open (the default is&nbsp;512). All types of connections (for example, connections with proxied servers) count against the maximum, not just client connections. But it\u2019s important to keep in mind that ultimately there is another limit on the number of simultaneous connections per worker: the operating system limit on the maximum number of file descriptors (FDs) allocated to each process. In modern UNIX distributions, the default limit is&nbsp;1024.<\/p>\n<p>For all but the smallest NGINX deployments, a limit of&nbsp;512 connections per worker is probably too small. Indeed, the default <strong>nginx.conf<\/strong> file we distribute with NGINX Open Source binaries and NGINX&nbsp;Plus increases it to&nbsp;1024. <\/p>\n<p>The common configuration mistake is not increasing the limit on FDs to at least twice the value of <code>worker_connections<\/code>. The fix is to set that value with the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/ngx_core_module.html#worker_rlimit_nofile\" rel=\"noopener noreferrer\"><code>worker_rlimit_nofile<\/code><\/a> directive in the main configuration context. <\/p>\n<p>Here\u2019s why more FDs are needed: each connection from an NGINX worker process to a client or upstream server consumes an FD. When NGINX acts as a web server, it uses one FD for the client connection and one FD per served file, for a minimum of two FDs per client (but most web pages are built from many files). When it acts as a proxy server, NGINX uses one FD each for the connection to the client and upstream server, and potentially a third FD for the file used to store the server\u2019s response temporarily. As a caching server, NGINX behaves like a web server for cached responses and like a proxy server if the cache is empty or expired.<\/p>\n<p>NGINX also uses an FD per log file and a couple FDs to communicate with master process, but usually these numbers are small compared to the number of FDs used for connections and files.<\/p>\n<p>UNIX offers several ways to set the number of FDs per process:<\/p>\n<ul>\n<li>The <code>ulimit<\/code> command if you start NGINX from a shell<\/li>\n<li>The <code>init<\/code> script or <code>systemd<\/code> service manifest variables if you start NGINX as a service<\/li>\n<li>The <strong>\/etc\/security\/limits.conf<\/strong>  file<\/li>\n<\/ul>\n<p>However, the method to use depends on how you start NGINX, whereas <code>worker_rlimit_nofile<\/code> works no matter how you start NGINX.<\/p>\n<p>There is also a system&#8209;wide limit on the number of FDs, which you can set with the OS\u2019s <span><code>sysctl<\/code> <code>fs.file-max<\/code><\/span> command. It is usually large enough, but it is worth verifying that the maximum number of file descriptors all NGINX worker processes might use (<span><code>worker_rlimit_nofile<\/code> <code>*<\/code>  <code>worker_connections<\/code><\/span>) is significantly less than <code>fs.file&#8209;max<\/code>. If NGINX somehow uses all available FDs (for example, during a DoS attack), it becomes impossible even to log in to the machine to fix the issue.<\/p>\n<h2 id=\"error_log-off\">Mistake 2: The <code>error_log<\/code> <code>off<\/code> Directive<\/h2>\n<p>The common mistake is thinking that the <span><code>error_log<\/code> <code>off<\/code><\/span> directive disables logging. In fact, unlike the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_log_module.html#access_log\" rel=\"noopener noreferrer\"><code>access_log<\/code><\/a> directive, <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/ngx_core_module.html#error_log\" rel=\"noopener noreferrer\"><code>error_log<\/code><\/a> does not take an <code>off<\/code> parameter. If you include the <span><code>error_log<\/code> <code>off<\/code><\/span> directive in the configuration, NGINX creates an error log file named <strong>off<\/strong> in the default directory for NGINX configuration files (usually <strong>\/etc\/nginx<\/strong>).<\/p>\n<p>We don\u2019t recommend disabling the error log, because it is a vital source of information when debugging any problems with NGINX. However, if storage is so limited that it might be possible to log enough data to exhaust the available disk space, it might make sense to disable error logging. Include this directive in the main configuration context:<\/p>\n<pre><code class=\"config\">error_log \/dev\/null emerg;<\/code><\/pre>\n<p>Note that this directive doesn\u2019t apply until NGINX reads and validates the configuration. So each time NGINX starts up or the configuration is reloaded, it might log to the default error log location (usually <strong>\/var\/log\/nginx\/error.log<\/strong>) until the configuration is validated. To change the log directory, include the <span><code>-e<\/code> <code>&lt;<em>error_log_location<\/em>&gt;<\/code><\/span> parameter on the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/switches.html\" rel=\"noopener noreferrer\"><code>nginx<\/code><\/a> command. <\/p>\n<h2 id=\"no-keepalives\">Mistake 3: Not Enabling Keepalive Connections to Upstream Servers<\/h2>\n<p>By default, NGINX opens a new connection to an upstream (backend) server for every new incoming request. This is safe but inefficient, because NGINX and the server must exchange  three packets to establish a connection and three or four to terminate it.<\/p>\n<p>At high traffic volumes, opening a new connection for every request can exhaust system resources and make it impossible to open connections at all. Here\u2019s why: for each connection <span>the 4-tuple<\/span> of source address, source port, destination address, and destination port must be unique. For connections from NGINX to an upstream server, three of the elements (the first, third, and fourth) are fixed, leaving only the source port as a variable. When a connection is closed, the Linux socket sits in the <code>TIME&#8209;WAIT<\/code> state for two minutes, which at high traffic volumes increases the possibility of exhausting the pool of available source ports. If that happens, NGINX cannot open new connections to upstream servers.<\/p>\n<p>The fix is to enable <a target=\"_blank\" href=\"https:\/\/en.wikipedia.org\/wiki\/HTTP_persistent_connection\" rel=\"noopener noreferrer\">keepalive connections<\/a> between NGINX and upstream servers&nbsp;&ndash; instead of being closed when a request completes, the connection stays open to be used for additional requests. This both reduces the possibility of running out of source ports and <a target=\"_blank\" href=\"https:\/\/en.wikipedia.org\/wiki\/HTTP_persistent_connection#Advantages\" rel=\"noopener noreferrer\">improves performance<\/a>.<\/p>\n<p>To enable keepalive connections:<\/p>\n<ul>\n<li>\n<p>Include the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#keepalive\" rel=\"noopener noreferrer\"><code>keepalive<\/code><\/a> directive in every <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#upstream\" rel=\"noopener noreferrer\"><code>upstream{}<\/code><\/a> block, to set the number of idle keepalive connections to upstream servers preserved in the cache of each worker process.<\/p>\n<p>Note that the <code>keepalive<\/code> directive does not limit the total number of connections to upstream servers that an NGINX worker process can open&nbsp;&ndash; this is a common misconception. So the parameter to <code>keepalive<\/code> does not need to be as large as you might think.<\/p>\n<p>We recommend setting the parameter to twice the number of servers listed in the <code>upstream{}<\/code> block. This is large enough for NGINX to maintain keepalive connections with all the servers, but small enough that upstream servers can process new incoming connections as well.<\/p>\n<p>Note also that when you specify a load&#8209;balancing algorithm in the <code>upstream{}<\/code> block&nbsp;&ndash; with the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#hash\" rel=\"noopener noreferrer\"><code>hash<\/code><\/a>, <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#ip_hash\" rel=\"noopener noreferrer\"><code>ip_hash<\/code><\/a>, <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#least_conn\" rel=\"noopener noreferrer\"><code>least_conn<\/code><\/a>, <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#least_time\" rel=\"noopener noreferrer\"><code>least_time<\/code><\/a>, or <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#random\" rel=\"noopener noreferrer\"><code>random<\/code><\/a> directive&nbsp;&ndash; the directive must appear above the <code>keepalive<\/code> directive. This is one of the rare exceptions to the general rule that the order of directives in the NGINX configuration doesn\u2019t matter.<\/p>\n<\/li>\n<li>\n<p>In the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#location\" rel=\"noopener noreferrer\"><code>location{}<\/code><\/a> block that forwards requests to an upstream group, include the following directives along with the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_pass\" rel=\"noopener noreferrer\"><code>proxy_pass<\/code><\/a> directive:<\/p>\n<pre><code class=\"config\">proxy_http_version 1.1;\r\nproxy_set_header   \"Connection\" \"\";<\/code><\/pre>\n<p>By default NGINX uses HTTP\/1.0 for connections to upstream servers and accordingly adds the <span><code>Connection:<\/code> <code>close<\/code><\/span> header to the requests that it forwards to the servers. The result is that each connection gets closed when the request completes, despite the presence of the <code>keepalive<\/code> directive in the <code>upstream{}<\/code> block.<\/p>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_http_version\" rel=\"noopener noreferrer\"><code>proxy_http_version<\/code><\/a> directive tells NGINX to use HTTP\/1.1 instead, and the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_set_header\" rel=\"noopener noreferrer\"><code>proxy_set_header<\/code><\/a> directive removes the <code>close<\/code> value from the <code>Connection<\/code> header.<\/p>\n<\/li>\n<\/ul>\n<h2 id=\"directive-inheritance\">Mistake 4: Forgetting How Directive Inheritance Works<\/h2>\n<p>NGINX directives are inherited downwards, or \u201coutside&#8209;in\u201d: a <em>child<\/em> context&nbsp;&ndash; one nested within another context (its <em>parent<\/em>)&nbsp;&ndash; inherits the settings of directives included at the parent level. For example, all <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#server\" rel=\"noopener noreferrer\"><code>server{}<\/code><\/a> and <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#location\" rel=\"noopener noreferrer\"><code>location{}<\/code><\/a> blocks in the <code>http{}<\/code> context inherit the value of directives included at the <code>http<\/code> level, and a directive in a <code>server{}<\/code> block is inherited by all the child <code>location{}<\/code> blocks in it. However, when the same directive is included in both a parent context and its child context, the values are not added together&nbsp;&ndash; instead, the value in the child context overrides the parent value.<\/p>\n<p>The mistake is to forget this \u201coverride rule\u201d for <em>array directives<\/em>, which can be included not only in multiple contexts but also multiple times within a given context. Examples include <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_set_header\" rel=\"noopener noreferrer\"><code>proxy_set_header<\/code><\/a> and <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_headers_module.html#add_header\" rel=\"noopener noreferrer\"><code>add_header<\/code><\/a>&nbsp;&ndash; having \u201cadd\u201d in the name of second makes it particularly easy to forget about the override rule. <\/p>\n<p>We can illustrate how inheritance works with this example for <code>add_header<\/code>:<\/p>\n<pre><code class=\"config\">http {\r\n    add_header X-HTTP-LEVEL-HEADER 1;\r\n    add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;\r\n\r\n    server {\r\n        listen 8080;\r\n        location \/ {\r\n            return 200 \"OK\";\r\n        } \r\n    }\r\n\r\n    server {\r\n        listen 8081;\r\n        add_header X-SERVER-LEVEL-HEADER 1;\r\n\r\n        location \/ {\r\n            return 200 \"OK\";\r\n        }\r\n\r\n        location \/test {\r\n            add_header X-LOCATION-LEVEL-HEADER 1;\r\n            return 200 \"OK\";\r\n        }\r\n\r\n        location \/correct {\r\n            add_header X-HTTP-LEVEL-HEADER 1;\r\n            add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;\r\n\r\n            add_header X-SERVER-LEVEL-HEADER 1;\r\n            add_header X-LOCATION-LEVEL-HEADER 1;\r\n            return 200 \"OK\";\r\n        } \r\n    }\r\n}<\/code><\/pre>\n<p>For the server listening on port&nbsp;8080, there are no <code>add_header<\/code> directives in either the <code>server{}<\/code> or <code>location{}<\/code> blocks. So inheritance is straightforward and we see the two headers defined in the <code>http{}<\/code> context: <\/p>\n<pre><code class=\"terminal\">% <strong>curl -is localhost:8081<\/strong>\r\nHTTP\/1.1 200 OK\r\nServer: nginx\/1.21.5\r\nDate: Mon, 21 Feb 2022 10:12:15 GMT\r\nContent-Type: text\/plain\r\nContent-Length: 2\r\nConnection: keep-alive\r\n<strong>X-HTTP-LEVEL-HEADER: 1\r\nX-ANOTHER-HTTP-LEVEL-HEADER: 1<\/strong>\r\nOK<\/code><\/pre>\n<p>For the server listening on port&nbsp;8081, there is an <code>add_header<\/code> directive in the <code>server{}<\/code> block but not in its child <span><code>location<\/code> <code>\/<\/code><\/span> block. The header defined in the <code>server{}<\/code> block overrides the two headers defined in the <code>http{}<\/code> context: <\/p>\n<pre><code class=\"terminal\">% <strong>curl -is localhost:8080<\/strong>\r\nHTTP\/1.1 200 OK\r\nServer: nginx\/1.21.5\r\nDate: Mon, 21 Feb 2022 10:12:20 GMT\r\nContent-Type: text\/plain\r\nContent-Length: 2\r\nConnection: keep-alive\r\n<strong>X-SERVER-LEVEL-HEADER: 1<\/strong>\r\nOK<\/code><\/pre>\n<p>In the child <span><code>location<\/code> <code>\/test<\/code><\/span> block, there is an <code>add_header<\/code> directive and it overrides both the header from its parent <code>server{}<\/code> block and the two headers from the <code>http{}<\/code> context:<\/p>\n<pre><code class=\"terminal\">% <strong>curl -is localhost:8080\/test<\/strong>\r\nHTTP\/1.1 200 OK\r\nServer: nginx\/1.21.5\r\nDate: Mon, 21 Feb 2022 10:12:25 GMT\r\nContent-Type: text\/plain\r\nContent-Length: 2\r\nConnection: keep-alive\r\n<strong>X-LOCATION-LEVEL-HEADER: 1<\/strong>\r\nOK<\/code><\/pre>\n<p>If we want a <code>location{}<\/code> block to preserve the headers defined in its parent contexts along with any headers defined locally, we must redefine the parent headers within the <code>location{}<\/code> block. That\u2019s what we\u2019ve done in the <span><code>location<\/code> <code>\/correct<\/code><\/span> block:<\/p>\n<pre><code class=\"terminal\">% <strong>curl -is localhost:8080\/correct<\/strong>\r\nHTTP\/1.1 200 OK\r\nServer: nginx\/1.21.5\r\nDate: Mon, 21 Feb 2022 10:12:30 GMT\r\nContent-Type: text\/plain\r\nContent-Length: 2\r\nConnection: keep-alive\r\n<strong>X-HTTP-LEVEL-HEADER: 1\r\nX-ANOTHER-HTTP-LEVEL-HEADER: 1\r\nX-SERVER-LEVEL-HEADER: 1\r\nX-LOCATION-LEVEL-HEADER: 1<\/strong>\r\nOK<\/code><\/pre>\n<h2 id=\"proxy_buffering-off\">Mistake 5: The <code>proxy_buffering<\/code> <code>off<\/code> Directive<\/h2>\n<p>Proxy buffering is enabled by default in NGINX (the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_buffering\" rel=\"noopener noreferrer\"><code>proxy_buffering<\/code><\/a> directive is set to <code>on<\/code>). Proxy buffering means that NGINX stores the response from a server in internal buffers as it comes in, and doesn\u2019t start sending data to the client until the entire response is buffered. Buffering helps to optimize performance with slow clients&nbsp;&ndash; because NGINX buffers the response for as long as it takes for the client to retrieve all of it, the proxied server can return its response as quickly as possible and return to being available to serve other requests. <\/p>\n<p>When proxy buffering is disabled, NGINX buffers only the first part of a server\u2019s response before starting to send it to the client, in a buffer that by default is one memory page in <span>size (4 KB<\/span> <span>or 8 KB<\/span> depending on the operating system). This is usually just enough space for the response header. NGINX then sends the response to the client synchronously as it receives it, forcing the server to sit idle as it waits until NGINX can accept the next response segment. <\/p>\n<p>So we\u2019re surprised by how often we see <span><code>proxy_buffering<\/code> <code>off<\/code><\/span> in configurations. Perhaps it is intended to reduce the latency experienced by clients, but the effect is negligible while the side effects are numerous: with proxy buffering disabled, rate limiting and caching don\u2019t work even if configured, performance suffers, and so on.<\/p>\n<p>There are only a small number of use cases where disabling proxy buffering might make sense (such as long polling), so we strongly discourage changing the default. For more information, see the <a target=\"_blank\" href=\"https:\/\/docs.nginx.com\/nginx\/admin-guide\/web-server\/reverse-proxy\/#configuring-buffers\" rel=\"noopener noreferrer\">NGINX&nbsp;Plus Admin Guide<\/a>. <\/p>\n<h2 id=\"if\">Mistake 6: Improper Use of the <code>if<\/code> Directive<\/h2>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_rewrite_module.html#if\" rel=\"noopener noreferrer\"><code>if<\/code><\/a> directive is tricky to use, especially in <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#location\" rel=\"noopener noreferrer\"><code>location{}<\/code><\/a> blocks. It often doesn\u2019t do what you expect and can even cause segfaults. In fact, it\u2019s so tricky that there\u2019s an article titled <a target=\"_blank\" href=\"https:\/\/www.nginx.com\/resources\/wiki\/start\/topics\/depth\/ifisevil\/\" rel=\"noopener noreferrer\">If is Evil<\/a> in the NGINX&nbsp;Wiki, and we direct you there for a detailed discussion of the problems and how to avoid them. <\/p>\n<p>In general, the only directives you can always use safely within an  <code>if{}<\/code> block are <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_rewrite_module.html#return\" rel=\"noopener noreferrer\"><code>return<\/code><\/a> and <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_rewrite_module.html#rewrite\" rel=\"noopener noreferrer\"><code>rewrite<\/code><\/a>. The following example uses <code>if<\/code> to detect requests that include the <code>X&#8209;Test<\/code> header (but this can be any condition you want to test for). NGINX returns <span>the <code>430<\/code> <code>(Request<\/code> <code>Header<\/code> <code>Fields<\/code> <code>Too<\/code> <code>Large)<\/code><\/span> error, intercepts it at the named location <strong>@error_430<\/strong> and proxies the request to the upstream group named <strong>b<\/strong>.<\/p>\n<pre><code class=\"config\">location \/ {\r\n    error_page 430 = @error_430;\r\n    if ($http_x_test) {\r\n        return 430; \r\n    }\r\n\r\n    proxy_pass http:\/\/a;\r\n}\r\n\r\nlocation @error_430 {\r\n    proxy_pass b;\r\n}<\/code><\/pre>\n<p>For this and many other uses of <code>if<\/code>, it\u2019s often possible to avoid the directive altogether. In the following example, when the request includes the <code>X&#8209;Test<\/code> header the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_map_module.html#map\" rel=\"noopener noreferrer\"><code>map{}<\/code><\/a> block sets the <code>$upstream_name<\/code> variable to <code>b<\/code> and the request is proxied to the upstream group with that name. <\/p>\n<pre><code class=\"config\">map $http_x_test $upstream_name {\r\n    default \"b\";\r\n    \"\"      \"a\";\r\n}\r\n\r\n# ...\r\n\r\nlocation \/ {\r\n    proxy_pass http:\/\/$upstream_name;\r\n}<\/code><\/pre>\n<h2 id=\"health-checks\">Mistake 7: Excessive Health Checks<\/h2>\n<p>It is quite common to configure multiple virtual servers to proxy requests to the same upstream group (in other words, to include the identical <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_pass\" rel=\"noopener noreferrer\"><code>proxy_pass<\/code><\/a> directive in multiple <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#server\" rel=\"noopener noreferrer\"><code>server{}<\/code><\/a> blocks). The mistake in this situation is to include a <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_hc_module.html#health_check\" rel=\"noopener noreferrer\"><code>health_check<\/code><\/a> directive in every <code>server{}<\/code> block. This just creates more load on the upstream servers without yielding any additional information. <\/p>\n<p>At the risk of being obvious, the fix is to define just one health check per <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#upstream\" rel=\"noopener noreferrer\"><code>upstream{}<\/code><\/a> block. Here we define the health check for the upstream group named <strong>b<\/strong> in a special named location, complete with appropriate timeouts and header settings.<\/p>\n<pre><code class=\"config\">location \/ {\r\n    proxy_set_header Host $host;\r\n    proxy_set_header \"Connection\" \"\";\r\n    proxy_http_version 1.1;\r\n    proxy_pass http:\/\/b;\r\n}\r\n\r\nlocation @health_check {\r\n    health_check;\r\n    proxy_connect_timeout 2s;\r\n    proxy_read_timeout 3s;\r\n    proxy_set_header Host example.com;\r\n    proxy_pass http:\/\/b;\r\n}<\/code><\/pre>\n<p>In complex configurations, it can further simplify management to group all health&#8209;check locations in a single virtual server along with the <span>NGINX Plus API<\/span> and dashboard, as in this example.<\/p>\n<pre><code class=\"config\">server {\r\n\tlisten 8080;\r\n \r\n\tlocation \/ {\r\n\t    # \u2026\r\n \t}\r\n \r\n\tlocation @health_check_b {\r\n\t    health_check;\r\n\t    proxy_connect_timeout 2s;\r\n\t    proxy_read_timeout 3s;\r\n\t    proxy_set_header Host example.com;\r\n\t    proxy_pass http:\/\/b;\r\n\t}\r\n \r\n\tlocation @health_check_c {\r\n\t    health_check;\r\n\t    proxy_connect_timeout 2s;\r\n\t    proxy_read_timeout 3s;\r\n\t    proxy_set_header Host api.example.com;\r\n\t    proxy_pass http:\/\/c;\r\n\t}\r\n \r\n\tlocation \/api {\r\n\t    api write=on;\r\n\t    # directives limiting access to the API (see 'Mistake 8' below)\r\n\t}\r\n \r\n\tlocation = \/dashboard.html {\r\n\t    root   \/usr\/share\/nginx\/html;\r\n\t}\r\n}<\/code><\/pre>\n<p>For more information about health checks for HTTP, TCP, UDP, and gRPC servers, see the <a target=\"_blank\" href=\"https:\/\/docs.nginx.com\/nginx\/admin-guide\/load-balancer\/\" rel=\"noopener noreferrer\">NGINX&nbsp;Plus Admin&nbsp;Guide<\/a>.<\/p>\n<h2 id=\"unsecured-metrics\">Mistake 8: Unsecured Access to Metrics<\/h2>\n<p>Basic metrics about NGINX operation are available from the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_stub_status_module.html\" rel=\"noopener noreferrer\">Stub Status<\/a> module. For NGINX&nbsp;Plus, you can also gather a much more extensive set of metrics with the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_api_module.html\" rel=\"noopener noreferrer\"><span>NGINX Plus API<\/span><\/a>. Enable metrics collection by including the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_stub_status_module.html#stub_status\" rel=\"noopener noreferrer\"><code>stub_status<\/code><\/a> or <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_api_module.html#api\" rel=\"noopener noreferrer\"><code>api<\/code><\/a> directive, respectively, in a <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#server\" rel=\"noopener noreferrer\"><code>server{}<\/code><\/a> or <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#location\" rel=\"noopener noreferrer\"><code>location{}<\/code><\/a> block, which becomes the URL you then access to view the metrics. (For the <span>NGINX Plus API<\/span>, you also need to configure shared memory zones for the NGINX entities&nbsp;&ndash; virtual servers, upstream groups, caches, and so on&nbsp;&ndash; for which you want to collect metrics; see the instructions in the <a target=\"_blank\" href=\"https:\/\/docs.nginx.com\/nginx\/admin-guide\/monitoring\/live-activity-monitoring\/\" rel=\"noopener noreferrer\">NGINX&nbsp;Plus Admin Guide<\/a>.)<\/p>\n<p>Some of the metrics are sensitive information that can be used to attack your website or the apps proxied by NGINX, and the mistake we sometimes see in user configurations is failure to restrict access to the corresponding URL. Here we look at some of the ways you can secure the metrics. We\u2019ll use <code>stub_status<\/code> in the first examples.<\/p>\n<p>With the following configuration, anyone on the Internet can access the metrics at <strong>http:\/\/example.com\/basic_status<\/strong>.<\/p>\n<pre><code class=\"config\">server {\r\n    listen 80;\r\n    server_name example.com;\r\n\r\n    location = \/basic_status {\r\n        stub_status;\r\n    }\r\n}<\/code><\/pre>\n<h3>Protect Metrics with HTTP Basic Authentication<\/h3>\n<p>To password&#8209;protect the metrics with <a target=\"_blank\" href=\"https:\/\/docs.nginx.com\/nginx\/admin-guide\/security-controls\/configuring-http-basic-authentication\/\" rel=\"noopener noreferrer\">HTTP Basic Authentication<\/a>, include the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_auth_basic_module.html\" rel=\"noopener noreferrer\"><code>auth_basic<\/code> and <code>auth_basic_user_file<\/code><\/a> directives. The file (here, <strong>.htpasswd<\/strong>) lists the usernames and passwords of clients who can log in to see the metrics:<\/p>\n<pre><code class=\"config\">server {\r\n    listen 80;\r\n    server_name example.com;\r\n\r\n    location = \/basic_status {\r\n        auth_basic \u201cclosed site\u201d;\r\n        auth_basic_user_file conf.d\/.htpasswd;\r\n        stub_status;\r\n    }\r\n}<\/code><\/pre>\n<h3>Protect Metrics with the allow and deny Directives<\/h3>\n<p>If you don\u2019t want authorized users to have to log in, and you know the IP addresses from which they will access the metrics, another option is the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_access_module.html#allow\" rel=\"noopener noreferrer\"><code>allow<\/code><\/a> directive. You can specify individual IPv4 and IPv6 addresses and CIDR ranges. The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_access_module.html#deny\" rel=\"noopener noreferrer\"><code>deny<\/code><\/a> <code>all<\/code> directive prevents access from any other addresses.<\/p>\n<pre><code class=\"config\">server {\r\n    listen 80;\r\n    server_name example.com;\r\n\r\n    location = \/basic_status {\r\n        allow 192.168.1.0\/24;\r\n        allow 10.1.1.0\/16;\r\n        allow 2001:0db8::\/32;\r\n        allow 96.1.2.23\/32;\r\n        deny  all;\r\n        stub_status;\r\n    }\r\n}<\/code><\/pre>\n<h3>Combining the Two Methods<\/h3>\n<p>What if we want to combine both methods? We can allow clients to access the metrics from specific addresses without a password and still require login for clients coming from different addresses. For this we use the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_core_module.html#satisfy\" rel=\"noopener noreferrer\"><code>satisfy<\/code><\/a> <code>any<\/code> directive. It tells NGINX to allow access to clients who either log in with HTTP Basic auth credentials or are using a preapproved IP address. For extra security, you can set <code>satisfy<\/code> to <code>all<\/code> to require even people who come from specific addresses to log in.<\/p>\n<pre><code class=\"config\">server {\r\n    listen 80;\r\n    server_name monitor.example.com;\r\n\r\n    location = \/basic_status {\r\n        satisfy any;\r\n\r\n        auth_basic \u201cclosed site\u201d;\r\n        auth_basic_user_file conf.d\/.htpasswd;\r\n        allow 192.168.1.0\/24;\r\n        allow 10.1.1.0\/16;\r\n        allow 2001:0db8::\/32;\r\n        allow 96.1.2.23\/32;\r\n        deny  all;\r\n        stub_status;\r\n    }\r\n}<\/code><\/pre>\n<p>With NGINX&nbsp;Plus, you use the same techniques to limit access to the <span>NGINX Plus API<\/span> endpoint (<strong>http:\/\/monitor.example.com:8080\/api\/<\/strong> in the following example) as well as the live activity monitoring dashboard at <strong>http:\/\/monitor.example.com\/dashboard.html<\/strong>.  <\/p>\n<p>This configuration permits access without a password only to clients coming from the&nbsp;96.1.2.23\/32 network or localhost. Because the directives are defined at the <code>server{}<\/code> level, the same restrictions apply to both the API and the dashboard. As a side note, the <code>write=on<\/code> parameter to <code>api<\/code> means these clients can also use the API to make configuration changes.<\/p>\n<p>For more information about configuring the API and dashboard, see the <a target=\"_blank\" href=\"https:\/\/docs.nginx.com\/nginx\/admin-guide\/monitoring\/live-activity-monitoring\/\" rel=\"noopener noreferrer\">NGINX&nbsp;Plus Admin Guide<\/a>. <\/p>\n<pre><code class=\"config\">server {\r\n    listen 8080;\r\n    server_name monitor.example.com;\r\n \r\n    satisfy any;\r\n    auth_basic \u201cclosed site\u201d;\r\n    auth_basic_user_file conf.d\/.htpasswd;\r\n    allow 127.0.0.1\/32;\r\n    allow 96.1.2.23\/32;\r\n    deny  all;\r\n\r\n    location = \/api\/ {    \r\n        api write=on;\r\n    }\r\n\r\n    location = \/dashboard.html {\r\n        root \/usr\/share\/nginx\/html;\r\n    }\r\n}<\/code><\/pre>\n<h2 id=\"ip_hash\">Mistake 9: Using <code>ip_hash<\/code> When All Traffic Comes from the Same \/24 CIDR Block<\/h2>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#ip_hash\" rel=\"noopener noreferrer\"><code>ip_hash<\/code><\/a> algorithm load balances traffic across the servers in an <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#upstream\" rel=\"noopener noreferrer\"><code>upstream{}<\/code><\/a> block, based on a hash of the client IP address. The hashing key is the first three octets of an IPv4 address or the entire IPv6 address. The method establishes session persistence, which means that requests from a client are always passed to the same server except when the server is unavailable.<\/p>\n<p>Suppose that we have deployed NGINX as a reverse proxy in a virtual private network configured for high availability. We put various firewalls, routers, Layer&nbsp;4 load balancers, and gateways in front of NGINX to accept traffic from different sources (the internal network, partner networks, the Internet, and so on) and pass it to NGINX for reverse proxying to upstream servers. Here\u2019s the initial NGINX configuration:<\/p>\n<pre><code class=\"config\">http {\r\n\r\n    upstream {\r\n        ip_hash;\r\n        server 10.10.20.105:8080;\r\n        server 10.10.20.106:8080;\r\n        server 10.10.20.108:8080;\r\n    }\r\n \r\n    server {# \u2026}\r\n}<\/code><\/pre>\n<p>But it turns out there\u2019s a problem: all of the \u201cintercepting\u201d devices are on the same&nbsp;10.10.0.0\/24 network, so to NGINX it looks like all traffic comes from addresses in that CIDR range. Remember that the <code>ip_hash<\/code> algorithm hashes the first three octets of an IPv4 address. In our deployment, the first three octets are the same&nbsp;&ndash;&nbsp;10.10.0&nbsp;&ndash; for every client, so the hash is the same for all of them and there\u2019s no basis for distributing traffic to different servers.<\/p>\n<p>The fix is to use the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#hash\" rel=\"noopener noreferrer\"><code>hash<\/code><\/a> algorithm instead with the <code>$binary_remote_addr<\/code> variable as the hash key. That variable captures the complete client address, converting it into a binary representation that <span>is 4 bytes<\/span> for an IPv4 address <span>and 16 bytes<\/span> for an IPv6 address. Now the hash is different for each intercepting device and load balancing works as expected.<\/p>\n<p>We also include the <code>consistent<\/code> parameter to use the <a target=\"_blank\" href=\"https:\/\/www.metabrew.com\/article\/libketama-consistent-hashing-algo-memcached-clients\" rel=\"noopener noreferrer\">ketama<\/a> hashing method instead of the default. This greatly reduces the number of keys that get remapped to a different upstream server when the set of servers changes, which yields a higher cache hit ratio for caching servers.<\/p>\n<pre><code class=\"config\">http {\r\n    upstream {\r\n        hash $binary_remote_addr consistent;\r\n        server 10.10.20.105:8080;\r\n        server 10.10.20.106:8080;\r\n        server 10.10.20.108:8080;\r\n    }\r\n\r\n    server {# \u2026}\r\n}<\/code><\/pre>\n<h2 id=\"upstream-groups\">Mistake 10: Not Taking Advantage of Upstream Groups<\/h2>\n<p>Suppose you are employing NGINX for one of the simplest use cases, as a reverse proxy for a single NodeJS&#8209;based backend application listening on port&nbsp;3000. A common configuration might look like this:<\/p>\n<pre><code class=\"config\">http {\r\n\r\n    server {\r\n        listen 80;\r\n        server_name example.com;\r\n\r\n        location \/ {\r\n            proxy_set_header Host $host;\r\n            proxy_pass http:\/\/localhost:3000\/;\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>Straightforward, right? The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_pass\" rel=\"noopener noreferrer\"><code>proxy_pass<\/code><\/a> directive tells NGINX where to send requests from clients. All NGINX needs to do is resolve the hostname to an IPv4 or IPv6 address. Once the connection is established NGINX forwards requests to that server.<\/p>\n<p>The mistake here is to assume that because there\u2019s only one server&nbsp;&ndash; and thus no reason to configure load balancing&nbsp;&ndash; it\u2019s pointless to create an <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#upstream\" rel=\"noopener noreferrer\"><code>upstream{}<\/code><\/a> block. In fact, an <code>upstream{}<\/code> block unlocks several features that improve performance, as illustrated by this configuration:<\/p>\n<pre><code class=\"config\">http {\r\n\r\n    upstream node_backend {\r\n        zone upstreams 64K;\r\n        server 127.0.0.1:3000 max_fails=1 fail_timeout=2s;\r\n        keepalive 2;\r\n    }\r\n\r\n    server {\r\n        listen 80;\r\n        server_name example.com;\r\n\r\n        location \/ {\r\n            proxy_set_header Host $host;\r\n            proxy_pass http:\/\/node_backend\/;\r\n            proxy_next_upstream error timeout http_500;\r\n\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#zone\" rel=\"noopener noreferrer\"><code>zone<\/code><\/a> directive establishes a shared memory zone where all NGINX worker processes on the host can access configuration and state information about the upstream servers. Several upstream groups can share the zone. With NGINX&nbsp;Plus, the zone also enables you to use the <span>NGINX Plus API<\/span> to change the servers in an upstream group and the settings for individual servers without restarting NGINX. For details, see the <a target=\"_blank\" href=\"https:\/\/docs.nginx.com\/nginx\/admin-guide\/load-balancer\/dynamic-configuration-api\/\" rel=\"noopener noreferrer\">NGINX&nbsp;Plus Admin Guide<\/a>.<\/p>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#server\" rel=\"noopener noreferrer\"><code>server<\/code><\/a> directive has several parameters you can use to tune server behavior. In this example we have changed the conditions NGINX uses to determine that a server is unhealthy and thus ineligible to accept requests. Here it considers a server unhealthy if a communication attempt fails even once within <span>each 2-second<\/span> period (instead of the default of once in <span>a 10-second<\/span> period). <\/p>\n<p>We\u2019re combining this setting with the <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_next_upstream\" rel=\"noopener noreferrer\"><code>proxy_next_upstream<\/code><\/a> directive to configure what NGINX considers a failed communication attempt, in which case it passes requests to the next server in the upstream group. To the default error and timeout conditions we add <code>http_500<\/code> so that NGINX considers an HTTP&nbsp;<span><code>500<\/code> <code>(Internal<\/code> <code>Server<\/code> <code>Error)<\/code><\/span> code from an upstream server to represent a failed attempt.<\/p>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#keepalive\" rel=\"noopener noreferrer\"><code>keepalive<\/code><\/a> directive sets the number of idle keepalive connections to upstream servers preserved in the cache of each worker process. We already discussed the benefits in <a href=\"#no-keepalives\">Mistake&nbsp;3: Not Enabling Keepalive Connections to Upstream Servers<\/a>. <\/p>\n<p>With NGINX&nbsp;Plus you can configure additional features with upstream groups: <\/p>\n<ul>\n<li>\n<p>We mentioned above that NGINX Open Source resolves server hostnames to IP addresses only once, during startup. The <code>resolve<\/code> parameter to the server directive enables NGINX&nbsp;Plus to monitor changes to the IP addresses that correspond to an upstream server\u2019s domain name, and automatically modify the upstream  configuration without the need to restart.<\/p>\n<p>The <code>service<\/code> parameter further enables NGINX&nbsp;Plus to use DNS <code>SRV<\/code> records, which include information about port numbers, weights, and priorities. This is critical in microservices environments where the port numbers of services are often dynamically assigned.<\/p>\n<p>For more information about resolving server addresses, see <a href=\"https:\/\/www.nginx.com\/blog\/dns-service-discovery-nginx-plus\/\">Using DNS for Service Discovery with NGINX and NGINX&nbsp;Plus<\/a> on our blog.<\/p>\n<\/li>\n<li>\n<p>The <code>slow_start<\/code> parameter to the server directive enables NGINX&nbsp;Plus to gradually increase the volume of requests it sends to a server that is newly considered healthy and available to accept requests. This prevents a sudden flood of requests that might overwhelm the server and cause it to fail again.<\/p>\n<\/li>\n<li>\n<p>The <a target=\"_blank\" href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#queue\" rel=\"noopener noreferrer\"><code>queue<\/code><\/a> directive enables NGINX&nbsp;Plus to place requests in a queue when it\u2019s not possible to select an upstream server to service the request, instead of returning an error to the client immediately.<\/p>\n<\/li>\n<\/ul>\n<h2>Resources<\/h2>\n<ul>\n<li><a target=\"_blank\" href=\"https:\/\/github.com\/yandex\/gixy\" rel=\"noopener noreferrer\">Gixy<\/a>, an NGINX configuration analyzer on GitHub<\/li>\n<li><a href=\"https:\/\/www.nginx.com\/products\/nginx-amplify\/\">NGINX Amplify<\/a>, which includes the <a target=\"_blank\" href=\"https:\/\/amplify.nginx.com\/docs\/guide-user-interface.html#analyzer\" rel=\"noopener noreferrer\">Analyzer<\/a> tool<\/li>\n<\/ul>\n<p>To try NGINX&nbsp;Plus, start your <span><a href=\"https:\/\/www.nginx.com\/free-trial-request\">free 30-day trial<\/a><\/span> today or <a href=\"https:\/\/www.nginx.com\/contact-sales\/\">contact us to discuss your use cases<\/a>.<\/p>\n<p>The post <a rel=\"nofollow\" href=\"https:\/\/www.nginx.com\/blog\/avoiding-top-10-nginx-configuration-mistakes\/\">Avoiding the Top 10 NGINX Configuration Mistakes<\/a> appeared first on <a rel=\"nofollow\" href=\"https:\/\/www.nginx.com\">NGINX<\/a>.<\/p>\n<p>Source: <a href=\"https:\/\/www.nginx.com\/blog\/avoiding-top-10-nginx-configuration-mistakes\/\" target=\"_blank\" rel=\"noopener\">Avoiding the Top 10 NGINX Configuration Mistakes<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"mh-excerpt\"><p>Avoiding the Top 10 NGINX Configuration Mistakes When we help NGINX users who are having problems, we often see the same configuration mistakes we\u2019ve seen over and over in other users\u2019 configurations&nbsp;&ndash; sometimes even in configurations written by fellow NGINX engineers! In this blog we look at&nbsp;10 of the most common errors, explaining what\u2019s wrong and how to fix it. Not enough file descriptors per worker The error_log off directive Not enabling keepalive connections to upstream servers Forgetting how directive inheritance works The proxy_buffering off directive Improper use of the if directive Excessive health checks Unsecured access to metrics Using ip_hash when all traffic comes from the same \/24 CIDR block Not taking advantage of upstream groups Mistake 1: Not Enough File Descriptors per Worker The worker_connections directive sets the maximum number of simultaneous connections that a NGINX worker process <a class=\"mh-excerpt-more\" href=\"https:\/\/jirak.net\/wp\/avoiding-the-top-10-nginx-configuration-mistakes\/\" title=\"Avoiding the Top 10 NGINX Configuration Mistakes\">[ more&#8230; ]<\/a><\/p>\n<\/div>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[169],"tags":[652],"class_list":["post-45295","post","type-post","status-publish","format-standard","hentry","category-news","tag-nginx"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/45295","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/comments?post=45295"}],"version-history":[{"count":1,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/45295\/revisions"}],"predecessor-version":[{"id":45296,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/45295\/revisions\/45296"}],"wp:attachment":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/media?parent=45295"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/categories?post=45295"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/tags?post=45295"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}