How To Move The Admin Site to a Subdomain in Django 4.0*
With the the help of django-hosts and a thousand tutorials
Ok, ok, Django is pretty neat! I’ve been putting off learning it for a while, but finally broke down. Most things are pretty straightforward so far, but this particularly problem gave me some trouble so here goes.
To be fair, some of these tutorials were helpful to get me started, but not quite what I wanted:
- https://www.migrmrz.dev/blog/configuring-and-setting-up-subdomains-on-django-using-django-hosts/
- https://ordinarycoders.com/blog/article/django-subdomains
Step 0: Setup Project Skeleton
I’m assuming that you’ve run something similar to these commands to get your app started (I’m just going with the Django tutorial here):
django-admin startproject mysite
cd mysite
python manage.py startapp polls
python mangae.py migrate
The created structure will look something like this:

Step 1: Download django-hosts & Apply Standard Configs
Download django-hosts with pip:
pip install django-hosts
I would add django-hosts
to your requirements.txt
as well. Then the next few steps are pretty standard.
- Add
django_hosts
toINSTALLED_APPS
in mysite/settings.py. - Add
‘django_hosts.middleware.HostsRequestMiddleware’
to the beginning ofMIDDLEWARE
in mysite/settings.py. - Add
‘django_hosts.middleware.HostsResponseMiddleware’
to the end ofMIDDLEWARE
in mysite/settings.py.
Step 2: Add app-specific but non-admin URLS to polls/urls.py and mysite/urls.py
My polls/urls.py looks something like this. Say your domain is something like website.com. view.index is what your user would see when they visit website.com, and views.some_url is what they see when they go to website.com/some-url.
from django.urls import path
from . import views
urlpatterns = [
path('', views.index),
path("some-url/", views.some_url)
]
My mysite/urls.py looks something like below. “polls.urls” points to polls/urls.py.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [path('', include("polls.urls"))]
Step 3: Create a separate admin urls file under polls/
I named mine urls_admin.py and it looks something like this:
from django.contrib import admin
from django.urls import path
urlpatterns = [path('', admin.site.urls),]
I don’t think this file needs to be under polls. You can probably put it under mysite/ right next to settings.py if that makes more sense so long as you can point to it.
Step 4: Create mysite/hosts.py and register the domains
This file should be in the same directory as settings.py.
from django_hosts import patterns, host
host_patterns = pattern(
'',
host(r'', mysite.urls, name=' '),
host(r'admin', 'polls.url_admins', name='admin')
The name
parameters are important, as you’ll refer to those in settings.py. The first parameter to host
is the subdomain of choice. If I were to specify host(r'abc', 'polls.url_admins', name='admin')
, then I would see the admin page if I go to abc.website.com.
Since we’re done adding files, here’s what the final tree structure should look like:

Step 5: Specify ROOT_HOSTCONF & DEFAULT_HOST
With the above input, my ROOT_HOSTCONF
and DEFAULT_HOST
are:
ROOT_HOSTCONF = 'mysite.hosts' # points to mysite/hosts.py
DEFAULT_HOST = ' ' # name in host(r'', mysite.urls, name=' ')
Step 6 (Optional): Add the (Sub)Domains to ALLOWED_HOSTS for Local Testing
My ALLOWED_HOSTS
looks like this:
ALLOWED_HOSTS = ["localhost", "admin.localhost"]
So, if you go to localhost:8000
, you’ll see the main page of your polls app, and admind.localhost:8000
will point to the admin page!
Wrap-Up
The amount of time it took me to put this together is kind of embarrassing, so I hope this helps the next person save time. When I was looking up solutions, it looks like Django has changed quite a bit through the years, so your guess is as good as mine on how long this solution stays relevant.
Stay Connected
I’d like to stay within the data science & ML space for my technical blog, but who am I kidding. There are way too many fun technologies about there to play with the limit myself. If you’re interested, join my email list, or become a Medium member (I’ll be receiving ~50% of your membership fees if you use this link) if you aren’t already. See you next post!