HTMX & Nginx

Page content

Little Test with HTMX & Nginx

recently, i saw the Keynote - “Full-Stack Python” (Andy “Pandy” Knight) and i read an article about html & websockets. So I thought why not give it a try?



the usual stuff:

  • Virtual Machine (here: OpenBSD VM)
  • FQDN Pointing to your Box
  • SSL Cert


on your webserver, create a new webroot wherever you have your pages located.

su - webmaster
mkdir -p /var/www/virtual/
cd /var/www/virtual/

the main part of the python code …

cat << 'EOF' >
from datetime import datetime

from fastapi import FastAPI, Request, WebSocket
from fastapi.templating import Jinja2Templates

templates = Jinja2Templates(directory="templates")

app = FastAPI()

async def get(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

async def time_handler(websocket: WebSocket):
    await websocket.accept()
    # response content
    content = """
        <div hx-swap-oob="beforeend:#content">
        {time}: {message}<br>
    while True:
        msg = await websocket.receive_json()
            #content.format(time=time.time(), message=msg["chat_message"])

        now =
        time = now.strftime("%H:%M:%S")
        await websocket.send_text(
            content.format(time=time, message=msg["chat_message"])


we need one webpage in the template folder

mkdir -p templates
cat << 'EOF' > templates/index.html
<!DOCTYPE html>
    <!-- include htmx -->
      src="[email protected]"

    <h1>Hello world</h1>

    <!-- websocket connection -->
    <div hx-ws="connect:/ws">
      <!-- input for new messages, send a message to the backend
      via websocket -->
      <form hx-ws="send:submit">
        <input name="chat_message" />

    <!-- location for new messages from the server -->
    <div id="content"></div>



cat << 'EOF' > pyproject.toml                                                                               
name = ""
version = "0.1.0"
description = ""
authors = ["<[email protected]>"]
readme = ""

python = "^3.10"
fastapi = "^0.95.1"
uvicorn = "^0.21.1"
jinja2 = "^3.1.2"
websockets = "^11.0.2"
wsproto = "^1.2.0"

requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

create virtualenv

poetry shell
poetry install


we need a reverse proxy in front of the application. nginx will terminate tls and do the logging

change to nginx vhosts directory

cd /etc/nginx/sites

add config

cat << 'EOF' >
upstream websocket {

# Redirect to https
server {

    listen        80;
    listen        [::]:80;

    location / {
        return 301    https://$host$request_uri;

# WebServer
server {

    listen        443 ssl;
    listen        [::]:443 ssl;

    access_log    /var/log/nginx/;
    error_log     /var/log/nginx/;

    ssl_certificate_key         /etc/ssl/certs/;
    ssl_certificate             /etc/ssl/certs/;

    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains;";

    location / {
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;

    location /ws {
      proxy_pass http://websocket;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
      proxy_set_header Host $host;


you need to adapt a few things. Path, Cert, FQDN …

Restart Services

afterwards, start / restart the nginx service …

rcctl restart nginx

run app

… and start the app from the shell

poetry run python3 -m uvicorn main:app --port=8123 --reload

open your browser

Any Comments ?

sha256: 0bbbad50481c17422697f333762f623ce1363607dcd4b50ec11430eab1e0a109