Fediverse Custom Domain with Nginx

I run a gotosocial instance since last year and wrote about it. The domain of the instance is fedi.cress.space, but I want @cress.space instead of @fedi.cress.space as domain for a user account. To get this a few redirects are needed. Jacobian wrote about this for Cloudflare Pages. For me the cress.space domain is already setup with nginx, so I wanted a solution that works with nginx.

The redirects needed in nginx (as an example for cress.space -> fedi.cress.space):

# webfinger
location /.well-known/webfinger {
  rewrite ^.*$ https://fedi.cress.space/.well-known/webfinger permanent;

location /.well-known/host-meta {
  rewrite ^.*$ https://fedi.cress.space/.well-known/host-meta permanent;

location /.well-known/nodeinfo {
  rewrite ^.*$ https://fedi.cress.space/.well-known/nodeinfo permanent;

The other change is in the gotosocial config (my change). I did this a while back on an empty gotosocial instance, so I don't know what happens if this is changed on an exsiting gotosocial database. For my setup both urls still work (the fedi.cress.space one and the cress.space one that is redirected), so I would assume changing it later works.

Django Requests Canceled by Client

We kind of plan to proxy an rest api call through a Django view. This is not the ideal solution but currently the best option. The result of the in between rest api is processed and saved in the database of the proxy Django. We return the same value that is saved into the database in the response of the view. The request in between is normally fast enough for timeouts, but what happens when it is not fast enough.

In this post I plan to reproduce this scenario with Django 5.0 and current psycopg3. We run sync views/models in Django so I will try this first. Because the result of the rest api call in the view is needed async will not help to speed this up, this post is using sync for everything. But our setup is with PostgreSQL and I may want to try this later with async this reproduction is using PostgreSQL too.

Steps in this post:

  • Running PostgreSQL in Docker.

  • Setting up Django.

  • Add model, url-route and view.

  • The actual experiment.

Running PostgreSQL

Running PostgreSQL in Docker container:

docker run -d --name mydb -e POSTGRES_PASSWORD=someSecretPasswordHere -p 5432:5432 postgres:latest

No volume needed because we don't need to persist the database after removing the container.

We need to create the database (here "mysite"). This can be done with psql, which I don't have on my system, so I used the one from the postgres Docker container like this:

docker run -it --rm postgres psql -h -U postgres
# then paste the password
# and create the db:

The ip adress is needed because we are in the Docker container and there we cannot access the port 5432 of another container via localhost. To get the ip address: docker inspect mydb | jq -r '.[0].NetworkSettings.IPAddress'.

Setting up Django

Install required packages and setup a new Django project:

# in a virtualenv
pip install django==5.0 psycopg[binary]==3.1.16
# init django project
django-admin startproject mysite
cd mysite
# create a new app
python manage.py startapp myapp

Setup changes needed in mysite/settings.py:

  • Add "myapp" to INSTALLED_APPS

  • And change database to postgresql (this is not the proposed way from the Django documentation!):

   "default": {
        "ENGINE": "django.db.backends.postgresql",
        "HOST": "localhost",
        "NAME": "mysite",
        "USER": "postgres",
        "PASSWORD": "someSecretPasswordHere",

Model, Route and View

Because we want to test if writing to the Database is still happening even if the request is interrupted we need a model, a view and a route to the view.

First we add a simple model to myapp/models.py:

from django.db import models

class SomeData(models.Model):
    key = models.CharField(max_length=42, unique=True)
    is_running = models.BooleanField(default=True)
    payload = models.JSONField(default=dict, blank=True)
    modified = models.DateTimeField(auto_now_add=True)

A route to our view in myapp/urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),

Change the urlpatterns and add an import for "include" in mysite/urls.py:

from django.urls import include, path

urlpatterns = [
    path("admin/", admin.site.urls),
    path("/", include("myapp.urls")),

The most important one: the view (saved to myapp/views.py):

import json
import time

from django.http import HttpResponse
from .models import SomeData

def index(request):
    key = request.GET.get("key")
    if key:
        obj, _ = SomeData.objects.update_or_create(
            defaults={"key": key, "is_running": True},

        # here some rest api call is happening

        # some results get processed and written to the database
        obj.payload = {"some": key}
        obj.is_running = False
        return HttpResponse(json.dumps(obj.payload))

    return HttpResponse("needs ?key=")

Reusing the obj after waiting for 5 seconds could cause problems. But on the other hand, a reprocessing should result in the same payload written to the database. So we don't care for now and will add a model-get when it is needed later.

The Experiment

We need to run the migrations and start the Django server:

python manage.py makemigrations myapp
python manage.py migrate
python manage.py runserver 8080

Now trigger a call but don't wait the 5 seconds but cancel the request after 2 seconds:

$ curl -m 2 http://localhost:8080/\?key\=unique_key
curl: (28) Operation timed out after 2001 milliseconds with 0 bytes received

Now see how the data in the database looks like by running python manage.py shell:

>>> from myapp.models import SomeData
>>> SomeData.objects.get(key="unique_key").__dict__
{'_state': <django.db.models.base.ModelState object at 0x7f1bdbf79f90>, 'id': 3, 'key': 'unique_key', 'is_running': False, 'payload': {'some': 'unique_key'}, 'modified': datetime.datetime(2023, 12, 21, 14, 20, 45, 754889, tzinfo=datetime.timezone.utc)}


It seems like the data is still written into the database, even if it is not returned. I verified this by adding some prints in the view and increased the sleep time to 20 seconds. Then I tried to run this with gunicorn (gunicorn mysite.wsgi -b to verify this is not a side effect of the devserver. Still the same is happening. 🎉

FastApi with PGMQ on Fly.io

Out of curiosity I want to explore alternatives to RabbitMQ. Todays alternative is a message queue as extension to PostgreSQL: PGMQ. To test this I deployed PGMQ to fly.io and connected a FastAPI Python app with psycopg3.

First the deployment of PGMQ. Because we want to store the data on a volume, we need a custom Dockerfile:

FROM quay.io/tembo/pgmq-pg
ENV PGDATA=/data/pgdata

and of course a fly app with a volume, the fly.toml for me looks like this:

app = "empty-dream-2474"
primary_region = "ams"

  internal_port = 5432
  protocol = "tcp"
  auto_stop_machines = false
  auto_start_machines = true
  min_machines_running = 1

  source = "pgmq_data"
  destination = "/data"
  processes = ["app"]

The app is on purpose not stopped when idle, because it wasn't waking up when connecting. Something so solve another day.

Finally the app needs a secret for the password, i.e.


When deployed this app will provide a PostgreSQL service on port 5432 to be used from another app in the same account. So I deployed a second fly.io app with FastAPI connecting to the PostgreSQL instance. All apps in the same account can connect to each other internally by their region and name, i.e. the address of my PostgreSQL instance is ams.empty-dream-2474.internal.

The full code of the FastAPI + psycopg3 on fly.io example: https://gist.github.com/mfa/1977381ef7f57c7884098c7dee233295.

Some remarks to the code:

  • Using parameters from a rest api, plugging them together to json and adding them to the psycopg3-SQL is messy.

  • The init of the extension and creation of queues for PGMQ needs to run once. This should probably happen when initializing the PostgreSQL app.

  • The two rest function are send and pop to add a message to the queue and pop the oldest one. For more possibities see the PGMQ documentation

The send call returns the current length of the queue:


The pop call returns the oldest message from the queue (I called already twice before, to get to this message):


As a result of this PGMQ exploration, I will probably not use this for an actual (hobby) project. I probably don't need the performance that is possible with this. And running a custom PostgreSQL with its own Dockerfile feels like too much future maintenance.

Both, database and FastAPI app, are not deployed anymore.