<p>I'm running an Nginx Ingress Controller installed via Helm on my Kubernetes cluster. I would like to change the HTML/CSS in the default backend service for some specific errors (e.g. 404). </p>

<p><a href="https://kubernetes.github.io/ingress-nginx/user-guide/default-backend/" rel="noreferrer">This</a> link provides  some general information about the default backend. However, there is no mention of how to actually customize the served HTML/CSS files. </p>

David
<h3>August 2021 update:</h3>
<p>The original answer contains the steps necessary to deploy a custom default backend on kubernetes.</p>
<p>However, the newest version of <a href="https://kubernetes.github.io/ingress-nginx/" rel="noreferrer">ingress-nginx</a> allows the user to only specify the docker image to pull - no need for other k8s resource files (i.e. service and deployment).</p>
<p>My current <code>values.yaml</code> which I'm using for the nginx ingress controller to allow a custom default backend:</p>
<pre><code>defaultBackend:
  enabled: true
  name: custom-default-backend
  image:
    repository: dvdblk/custom-default-backend
    tag: &quot;latest&quot;
    pullPolicy: Always
  port: 8080
  extraVolumeMounts:
    - name: tmp
      mountPath: /tmp
  extraVolumes:
    - name: tmp
      emptyDir: {}
</code></pre>
<p>Here is the <a href="https://github.com/dvdblk/custom-default-backend" rel="noreferrer">GitHub repo</a> of the custom backend.</p>
<hr />
<h3>Older answer:</h3>
<p>Alright, some parts of these answers were helpful on the hunt for the complete solution, especially the one from @<a href="https://stackoverflow.com/users/1318694/matt">Matt</a>. However, it took me quite some time to get this working so I've decided to write my own answer with all the <em>necessary</em> details that others might struggle with as well.</p>
<p>The first thing would be to create a Docker image server capable of responding to any request with 404 content, except <code>/healthz</code> and <code>/metrics</code>. As @<a href="https://stackoverflow.com/users/1318694/matt">Matt</a> mentioned this could be an Nginx instance (which I've used). To sum up:</p>
<ul>
<li><code>/healthz</code> should return <code>200</code></li>
<li><code>/metrics</code> is optional, but it should return data that is readable by Prometheus in case you are using it for k8s metrics. Nginx can provide some basic data that Prometheus can read. Consider this <a href="https://dimitr.im/monitoring-nginx-with-prometheus-and-grafana" rel="noreferrer">link</a> if you would like to get the full Prometheus integration with Nginx.</li>
<li><code>/</code> returns a 404 with your custom HTML content.</li>
</ul>
<p>Thus, the <code>Dockerfile</code> looks like this:</p>
<pre><code>FROM nginx:alpine

# Remove default NGINX Config
# Take care of Nginx logging
RUN rm /etc/nginx/conf.d/default.conf &amp;&amp; \
    ln -sf /dev/stdout /var/log/nginx/access.log &amp;&amp; \
    ln -sf /dev/stderr /var/log/nginx/error.log

# NGINX Config
COPY ./default.conf /etc/nginx/conf.d/default.conf

# Resources
COPY content/ /var/www/html/

CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]
</code></pre>
<p>In the same folder where Dockerfile is located, create this <code>default.conf</code> Nginx configuration file:</p>
<pre><code>server {
    root /var/www/html;
    index 404.html;

    location / {
        
    }

    location /healthz {
        access_log off;
        return 200 &quot;healthy\n&quot;;
    }
    
    location /metrics {
        # This creates a readable and somewhat useful response for Prometheus
        stub_status on;
    }

    error_page 404 /404.html;
    location = /404.html {
        internal;
    }
}
</code></pre>
<p>At last, provide a <code>content/404.html</code> file with HTML/CSS to your own liking.</p>
<p>Now build the Docker image with:</p>
<pre><code>docker build --no-cache -t custom-default-backend .
</code></pre>
<p>Tag this image so that it is ready to be pushed into DockerHub (or your own private docker registry):</p>
<pre><code>docker tag custom-default-backend:latest &lt;your_dockerhub_username&gt;/custom-default-backend
</code></pre>
<p>Push the image to a DockerHub repository:</p>
<pre><code>docker push &lt;your_dockerhub_username&gt;/custom-default-backend
</code></pre>
<p>Now comes the part of integrating this custom-default-backend image into the Helm installation. In order to do this, we first need to create this k8s resource file (<code>custom_default_backend.yaml</code>):</p>
<pre><code>---
apiVersion: v1
kind: Service
metadata:
  name: custom-default-backend
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: custom-default-backend
    app.kubernetes.io/part-of: ingress-nginx
spec:
  selector:
    app.kubernetes.io/name: custom-default-backend
    app.kubernetes.io/part-of: ingress-nginx
  ports:
  - port: 80
    targetPort: 80
    name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-default-backend
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: custom-default-backend
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: custom-default-backend
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: custom-default-backend
        app.kubernetes.io/part-of: ingress-nginx
    spec:
      containers:
      - name: custom-default-backend
        # Don't forget to edit the line below
        image: &lt;your_dockerhub_username&gt;/custom-default-backend:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 80
</code></pre>
<p>Assuming we have a k8s namespace <code>ingress-nginx</code> already created we can create these two resources.</p>
<pre><code>kubectl apply -f custom_default_backend.yaml
</code></pre>
<p>Now in order to tie the Nginx Ingress Controller with our new service, we could probably just edit the deployment of the Ingress Controller. But I've decided to remove it completely via Helm:</p>
<pre><code>helm delete nginx-ingress -n ingress-nginx
</code></pre>
<p>And install it again with this command (make sure you have the <code>--set</code> flag with proper arguments included):</p>
<pre><code>helm install nginx-ingress --namespace ingress-nginx stable/nginx-ingress --set defaultBackend.enabled=false,controller.defaultBackendService=ingress-nginx/custom-default-backend
</code></pre>
<p>With these steps you should end up with a working custom default backend implementation. <a href="https://github.com/dvdblk/custom-default-backend" rel="noreferrer">Here</a> is a GitHub repo with the files that I have used in this answer.</p>
