Infrastructure Series -- Use NGINX to inject CSS themes

table of contents

MODULAR THEMEING!

Y’all had to see this one coming. Lets rice up theme our webpages via NGINX. Ever have a page you wish looked different but you do not want to modify the software. You want to leave it clean and easy to manage. You want your theming modifications to be separate and easy to manage. This is the guide for you.

Obligatory shill of blog stream post: Phaselockedloopable- PLL’s continued exploration of networking, self-hosting and decoupling from big tech

As always check for updates in the second post :wink:


Overview

So most pages use style-sheets (CSS) to determine how they look. I host several services for myself and a lot of them make CSS customization hard or impossible and revert it on every update. So lets change that. Lets separate the configurations from the actual service files and dockers :wink:

Much like any reverse proxy NGINX has a substitute model for such injection. You can find their basic documentation on this here. Module ngx_http_sub_module . Thats exactly what we are going to do. We are going to tell the proxy’d service to accept encoding and allow us to substitute parts of its page. Now keep in mind we will have to monitor what we do in order to prevent issues with our security headers which you should have set. Heres a guide to it Infrastructure Series -- HTTP(S) Security Headers! You should use them! [NGINX] (shameless shill 2.0). If you use third party themes which I do sometimes you can either copy all there stuff via a gitpull and serve it locally or you can use their URL. If you use their URL remember to place it within your CSP so that you dont display the theme improperly :wink:

The Procedure

I take a strikingly different approach than the base documentation. This is because I do not like including this all in one file. I think thats messy and hard to debug. So what we are going to do is use a very clever feature of NGINX called mapping to help us do this and separate the themes into configuration files. This will make per service troubleshooting easier (in theory).

To do this we need to ensure we create the proper directory structures to make for this simple variable switch. You can also setup NGINX to use variables to change the themes. This will update the theme on all your location blocks by just changing a variable. This also means we need to setup additional variables to define what conf is what applications. Bare with me here I think this process is worth it or maybe I’m just a crazy engineer with too much time on my hands (not really).

Create a map in your HTTP block

We achieve this by inserting the following:

# Themes Mapping
map $host $theme {
    default $themeName; # Ex: default (theme-name)
}

Now you can make multiple maps if you have different themes. However I do go for a consistent set of themes that I name the same thing but with a different service so that I can just use one master switch. YMMV depending on how you structure your theming.

Now make a folder within /etc/nginx/ called themes.d. We will use this folder to store all of our theme configurations. This will be useful for separating them from higher level configurations. Also make a CSS folder within it so that the tree within the NGINX folder looks like this

├── themes.d
│   ├── css-*//
│   └── themes.conf

Within that folder make sure your create a $APP folder for every application name and if you have multiple themes place them in there with different names *.css.

We also need to tell NGINX to serve the CSS files if we store them locally (I do). Admittedly this is the simplest and jankiest way to do so. This is why creating that additional directory was important. We dont want to serve our configurations just the CSS files. Also subtle trick. This block is case INSENSITIVE ;). I placed this in the themes.< MY-TLD >.net server block conf. If you dont know how to make server blocks. Please read the documentation. You should not be at this stage if you have not.

  location ~* \.(js|jpg|png|css)$ {
    root /etc/nginx/themes.d/css/;
  }

Now in the themes.conf file we want to define where our CSS style sheets are at and what application will be used/their name with a simple variable switch. It is going to serve as the link for where we store everything. You may also define a themes-ext.conf if you have externally hosted stuff and define it properly for the pathing to those.

    proxy_set_header Accept-Encoding "";
    sub_filter
    '</head>'
    '<link rel="stylesheet" type="text/css" href="https://themes.< MY-TLD >.net/$appName/$theme.css">
    </head>';
    sub_filter_once on;

Bingo these switches make it easy to define the and the . Now we can get to organizing all of that. As you can see the URL has variables in it /$app/$theme.css The $theme variable is set in the http block and will affect all server blocks. And the $app variable is set in the location block. SO make SURE your names are CONSISTENT per THEME per APP.

Now all we have to go into is each services HTTP conf location block. (usually the root / Location…) and define the variable plus where to include it from. In this case the themes conf file.

    set $appName <app>;
    set $themeName <theme-name>;
    include /etc/nginx/themes.d/themes.conf;

That should do the application/theme switch for us. Pretty suite. It allows us more fine granular control of multiple themes

Apply the work (Example)

Im going to do bitwarden. I have written my own CSS for this. I will show you the end results of this.

So assuming you have done the map correct and structured your directories properly

image

Simply edit your configuration of your HTTP server block for bitwarden and add the following:

	set $appName bitwarden;
	set $themeName royal;
	include /etc/nginx/themes.d/themes.conf;

WITHIN the root location block

Make sure you modify your CSP for any URLs that you have added. In my case it was themes.< MY-TLD >.net

Thus my CSP is now:

add_header Content-Security-Policy "  default-src 'none'; connect-src 'self'; font-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://themes.< MY-TLD >.net/bitwarden/ ;" always;

Bingo here is the result!:

Royal purple baby!

This also allows you to test themes by making a seperate test file and preserve your old one so that you dont lose it. All you have to do is change a variable :wink: :wink: . Come on thats so easy your very non tehcnical artistic wonderful vegan environmentalist girlfriend could do it. (please dont tell her I said that) I still have a lot of tweaking to do to the Bitwarden Rust container theme. Its not how I like it but its a start!

I hope yall find this helpful

Good Luck

Links to Infrastructure Series and Other Resources

Blog: Phaselockedloopable- PLL’s continued exploration of networking, self-hosting and decoupling from big tech

Phaselockedloopable- PLL’s continued exploration of networking, self-hosting and decoupling from big tech

Series 1: Native Dual Stack IP4+IP6

Infrastructure Series – Native Dual Stack IP4+IP6

Series 2: Wireguard Site to Site Tunnel

Infrastructure Series – Wireguard Site to Site Tunnel

Series 3: Recursive DNS and Adblocking DNS over TLS w/NGINX

Infrastructure Series – Recursive DNS and Adblocking DNS over TLS w/NGINX

Series 4: NGINX Reverse Proxy and Hardening SSL

Infrastructure Series – NGINX Reverse Proxy and Hardening SSL

Series 5: Taking DNS One Step Further - Full DNS Server infrastructure

Infrastructure Series – Taking DNS One Step Further - Full DNS Server infrastructure

Series 6: HTTP(S) Security Headers! You should use them!

Infrastructure Series – HTTP(S) Security Headers! You should use them! [NGINX]

Series 7: Use NGINX to inject CSS themes

Infrastructure Series – Use NGINX to inject CSS themes

ONE KEY TO RULE THEM ALL

Setting up a YubiKey Properly – One Key to rule them ALL!

Series 9: Infrastructure Series: BIND9 Authoritative DNS Guide “Please See Me Edition”

Infrastructure Series: BIND9 Authoritative DNS Guide “Please See Me Edition”

Buy me a crypto-beer

If you found this guide helpful you can donate Monero or Bitcoin to me at the following address in my User Card Profile

2 Likes

Reserved


Note 1:

Jellyfin can be a bit finicky

You may have to install your CSS to the custom CSS spot as well

1 Like

I might do a guide on modularizing configs

I did not know this was possible to do in nginx, or at least not to do fairly easily.

Thanks for writing this up, I’ll look into implementing something like this when I get back around to working on my nginx config.

1 Like

Anytime

Keeping a nice modular folder and a good way to lay out all your security headers ETC is the key to having an easily debuggable situation when it comes to your web server

It’s extra work but it’s not hard