{"id":1320,"date":"2023-05-20T10:26:47","date_gmt":"2023-05-20T15:26:47","guid":{"rendered":"https:\/\/www.nathanhunstad.com\/blog\/?p=1320"},"modified":"2023-05-20T10:26:47","modified_gmt":"2023-05-20T15:26:47","slug":"how-to-configure-kibana-behind-an-nginx-proxy","status":"publish","type":"post","link":"https:\/\/www.nathanhunstad.com\/blog\/2023\/05\/how-to-configure-kibana-behind-an-nginx-proxy\/","title":{"rendered":"How to configure Kibana behind an nginx proxy"},"content":{"rendered":"\n<p>In my <a href=\"https:\/\/www.nathanhunstad.com\/blog\/2023\/04\/my-almost-effortless-upgrade-experience-and-lessons-learned-with-elastic-8\/\" target=\"_blank\" rel=\"noopener\" title=\"\">previous post<\/a> about upgrading to Elastic 8, I signed off with the promise of sharing how I put Kibana behind an nginx proxy. Here&#8217;s the post on how I did that, and what I did to make it work after a few hours of messing around with various settings. If you want the TL;DR on the biggest lesson learned, it is &#8220;Delete your cookies!&#8221; See below for why that matters.<\/p>\n\n\n\n<p>First, a brief reminder of the defaults for Kibana: if you don&#8217;t change anything, Kibana will listen on port 5601 on unencrypted HTTP. While that&#8217;s fine for testing, passing things like passwords over unencrypted HTTP isn&#8217;t a good idea security-wise. Plus, Edge browser won&#8217;t offer to save your username and password to log in automatically, nor should it because it is insecure! This was a hassle to me that I wanted to fix. To solve this, you can either enable TLS directly in Kibana, or you can put it behind a proxy like nginx and terminate TLS there.<\/p>\n\n\n\n<p>If you do want to enable it directly in Kibana, there&#8217;s <a href=\"https:\/\/www.elastic.co\/guide\/en\/kibana\/current\/configuring-tls.html\" target=\"_blank\" rel=\"noopener\" title=\"\">good documentation available<\/a>. Basically, create a private key and certificate, configure it in kibana.yml, set &#8220;server.ssl.enabled&#8221; to &#8220;true&#8221;  in the kibana.yml file, and you are good to go. This enables TLS, but still on the default port of 5601.<\/p>\n\n\n\n<p>If you want to move ports though, you can&#8217;t just set Kibana to listen on port 443. In *nix systems, only root can bind to privileged ports, defined as ports below 1024. Yes, you <a href=\"https:\/\/openthreat.ro\/kibana-unable-to-bind-port-80-or-443\/\" target=\"_blank\" rel=\"noopener\" title=\"\">can do things<\/a> to make Kibana bind to port 443, but another option is to use a web server that&#8217;s already set up to listen on port 443, such as nginx, and use it as a reverse proxy.<\/p>\n\n\n\n<p>With this setup, nginx will listen on port 443 and terminate the TLS connection, and pass traffic to port 5601 unencrypted so Kibana can respond. It&#8217;s important to make sure Kibana is only listening on localhost and not a public interface so traffic has to go through the proxy to ensure that there are no non-local unencrypted sessions being created.<\/p>\n\n\n\n<p>Besides the benefit of not having to mess with Kibana to allow it to bind to port 443, another benefit of using a reverse proxy is that it can proxy to many apps. For example, you can direct &#8220;https:\/\/FDQN\/kibana&#8221; to the Kibana app, and &#8220;https:\/\/FQDN\/elastic&#8221; to the Elastic HTTP listener if you want. Fortunately, Kibana can handle this &#8220;server.basePath&#8221; configuration item, so it knows that URLs forwarded from nginx start with &#8220;\/kibana&#8221; for example (although this is configurable).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting it up<\/h2>\n\n\n\n<p>My initial stab at getting this to work was to use the following nginx configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {\n\n        # SSL configuration\n        #\n        listen 443 ssl default_server;\n        listen &#91;::]:443 ssl default_server;\n        ssl on;\n        ssl_certificate \/etc\/ssl\/certs\/kibana.crt;\n        ssl_certificate_key \/etc\/ssl\/private\/kibana.key;\n\n        root \/var\/www\/html;\n\tlocation \/kibana {\n                proxy_pass http:\/\/127.0.0.1:5601\/kibana;\n        }\n        location \/ {\n                # First attempt to serve request as file, then\n                # as directory, then fall back to displaying a 404.\n                try_files $uri $uri\/ =404;\n        }\n}<\/code><\/pre>\n\n\n\n<p>This uses <a href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html#proxy_pass\" target=\"_blank\" rel=\"noopener\" title=\"\">proxy_pass<\/a> to forward any traffic sent to &#8220;\/kibana*&#8221; to the Kibana server listening on localhost. The relevant part of kibana.yml is here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server.port: 5601\nserver.publicBaseUrl: \"http:\/\/localhost:5601\/kibana\"\nserver.host: \"localhost\"\nserver.basePath: \"\/kibana\"\n# Specifies whether Kibana should rewrite requests that are prefixed with\n# `server.basePath` or require that they are rewritten by your reverse proxy.\n# This setting was effectively always `false` before Kibana 6.3 and will\n# default to `true` starting in Kibana 7.0.\nserver.rewriteBasePath: true\n<\/code><\/pre>\n\n\n\n<p>This listens on port 5601 on localhost. The &#8220;\/kibana&#8221; prefix is included in the publicBaseUrl and basePath to let Kibana know that URLs will be prefixed and ignore that part. The &#8220;server.rewriteBasePath&#8221; as stated will rewrite the requests to remove the &#8220;\/kibana&#8221; prefix specified. As stated above, there are two options to configure this: have Kibana rewrite the URLs, or nginx. Here we are having Kibana do it.<\/p>\n\n\n\n<p>After restarting both nginx and Kibana, and going to https:\/\/FQDN\/kibana, we got the login page. An auspicious start!<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/04\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"521\" height=\"464\" src=\"https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/04\/image-1.png\" alt=\"\" class=\"wp-image-1323\" srcset=\"https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/04\/image-1.png 521w, https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/04\/image-1-300x267.png 300w\" sizes=\"auto, (max-width: 521px) 100vw, 521px\" \/><\/a><figcaption class=\"wp-element-caption\">This is looking good so far!<\/figcaption><\/figure>\n\n\n\n<p>However, when I tried to log in, I just got sent back to the login page in a loop. Time for some troubleshooting.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">When in doubt, change everything<\/h2>\n\n\n\n<p>I first started by swapping the URL rewrites: instead of having Kibana do it, I had nginx do it. I flipped &#8220;server.rewriteBasePath&#8221; to false in Kibana, and added this line to the nginx config:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rewrite \/kibana\\\/?(.*)$ \/$1 break;<\/code><\/pre>\n\n\n\n<p>I was able to see the login page again, but still had the loop. Since that didn&#8217;t work and I didn&#8217;t want nginx to rewrite things, I put things back to where they were before.<\/p>\n\n\n\n<p>Getting nowhere, I needed more info. The next step was to set Kibana logging to DEBUG in the hopes that the error messages would be a bit more useful. I used the <a href=\"https:\/\/www.elastic.co\/guide\/en\/kibana\/current\/log-settings-examples.html\" target=\"_blank\" rel=\"noopener\" title=\"\">logging documentation<\/a> to set everything to DEBUG, and found the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">[2023-04-28T21:19:04.789+00:00][DEBUG][http.server.response] GET \/login?next=%2Fkibana%2F 200 61ms - 89.9KB\n[2023-04-28T21:19:04.877+00:00][DEBUG][http.server.response] GET \/node_modules\/@kbn\/ui-framework\/dist\/kui_light.min.css 304 29ms\n[2023-04-28T21:19:04.883+00:00][DEBUG][http.server.response] GET \/ui\/legacy_light_theme.min.css 304 28ms\n[2023-04-28T21:19:04.886+00:00][DEBUG][http.server.Kibana.cookie-session-storage] Error: Unauthorized\n[2023-04-28T21:19:04.887+00:00][DEBUG][plugins.security.basic.basic] Trying to authenticate user request to \/bootstrap-anonymous.js.\n[2023-04-28T21:19:04.889+00:00][DEBUG][plugins.security.http] Trying to authenticate user request to \/bootstrap-anonymous.js.\n[2023-04-28T21:19:04.890+00:00][DEBUG][plugins.security.http] Authorization header is not presented.\n[2023-04-28T21:19:04.891+00:00][DEBUG][plugins.security.authentication] Could not handle authentication attempt\n<\/pre>\n\n\n\n<p>&#8220;Authorization header is not presented&#8221; seemed promising, and <a href=\"https:\/\/github.com\/elastic\/kibana\/blob\/1bfeab7553899efcfa9a6e46b37dc3c7681dcf3b\/x-pack\/plugins\/security\/server\/authentication\/providers\/http.ts\" target=\"_blank\" rel=\"noopener\" title=\"\">looking in the code<\/a> indicated this error exists when the header isn&#8217;t present. Some more Googling led me to try adding the authorization headers to the nginx configuration explicitly, plus a few more in the &#8220;throw things at the wall&#8221; kind of approach:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>proxy_set_header   X-Real-IP        $remote_addr;\nproxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;\nproxy_set_header   X-Forwarded-User $http_authorization;\nproxy_set_header  Authorization $http_authorization;\nproxy_pass_header Authorization;\n<\/code><\/pre>\n\n\n\n<p>This did not work, which should be no surprise if you remember the TL;DR from above. There I sat, stuck for a while, until I noticed that one of the error messages was coming from the &#8220;http.server.Kibana.cookie-session-storage&#8221; service. When reading the <a href=\"https:\/\/www.elastic.co\/guide\/en\/kibana\/current\/xpack-security-session-management.html\" target=\"_blank\" rel=\"noopener\" title=\"\">cookie documentation<\/a> didn&#8217;t provide any obvious answers, I simply did what I should have done at the beginning: I deleted all of the cookies I had stored. Et voil\u00e0! I was able to get everything working again.<\/p>\n\n\n\n<p>I&#8217;m not entirely sure what happened, but my guess is that since I had some cookies from before I started this whole mess, that was interfering with the login process. &#8220;Try deleting your cookies&#8221; is step one of website login troubleshooting, which is something I forgot!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Not done yet<\/h2>\n\n\n\n<p>While Kibana itself was working just fine, I soon discovered a new problem: Metricbeat was broken. &#8220;Of course&#8221;, I thought to myself, &#8220;the Kibana monitoring module needs updating&#8221;. And it did, to be pointed at the new base path, since Kibana was handling it, not nginx:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  hosts: &#91;\"http:\/\/localhost:5601\"]\n  basepath: \"\/kibana\"\n<\/code><\/pre>\n\n\n\n<p>You may ask why I didn&#8217;t go through the nginx proxy to monitor this? Since Metricbeat is local to the server, there&#8217;s no benefit to doing so and it would add an unnecessary hop.<\/p>\n\n\n\n<p>However, after restarting Metricbeat I was still getting errors about Kibana not being accessible. I had forgotten that in addition to the module, the metricbeat.yml config file itself has a Kibana configuration section for all of its dashboards, and it needed to be updated with a slightly different config:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>setup.kibana:\n  host: \"http:\/\/localhost:5601\"\n  path: \"\/kibana\"\n<\/code><\/pre>\n\n\n\n<p>Once that was updated, everything was working as expected and nginx, Kibana, and Metricbeat were all getting along nicely.<\/p>\n\n\n\n<p>I glossed over the part about creating the certificate with my internal PKI. I&#8217;ll have the specifics about that in a future post, including a cameo by ChatGPT.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my previous post about upgrading to Elastic 8, I signed off with the promise of sharing how I put Kibana behind an nginx proxy. Here&#8217;s the post on how I did that, and what I did to make it work after a few hours of messing around with various settings. If you want the&hellip; <a class=\"more-link\" href=\"https:\/\/www.nathanhunstad.com\/blog\/2023\/05\/how-to-configure-kibana-behind-an-nginx-proxy\/\">Continue reading <span class=\"screen-reader-text\">How to configure Kibana behind an nginx proxy<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[127,19],"tags":[291],"class_list":["post-1320","post","type-post","status-publish","format-standard","hentry","category-security","category-tech-2","tag-kibana","entry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts\/1320","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/comments?post=1320"}],"version-history":[{"count":2,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts\/1320\/revisions"}],"predecessor-version":[{"id":1324,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts\/1320\/revisions\/1324"}],"wp:attachment":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/media?parent=1320"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/categories?post=1320"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/tags?post=1320"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}