When users visit a page that does not exist, Django shows a default error page. That page is useful during development, but in a real website it is better to display a clean and friendly page that matches your design. In this tutorial, you will learn how to handle 404 errors and create custom error pages in Django.
By the end, you will know how to:
404.html,A 404 error means:
The server is working, but the page the user requested does not exist.
For example:
Example:
/blog/python-basics/ → page exists → works/blog/python-basics-xyz/ → page does not exist → 404 errorA 404 error is normal in web applications. What matters is how you handle it.
The default Django 404 page is not ideal for public users because:
A custom 404 page is better because it can:
Django shows a 404 page when:
Http404.So there are two common cases:
Example:
# urls.py
urlpatterns = [
path('', views.home, name='home'),
]If the user visits /about/ and there is no matching route, Django returns a 404.
Example:
from django.http import Http404
from .models import Post
def post_detail(request, post_id):
try:
post = Post.objects.get(id=post_id)
except Post.DoesNotExist:
raise Http404("Post not found")
return render(request, 'blog/post_detail.html', {'post': post})Here, the route exists, but the specific object does not.
You can raise a 404 error using Http404.
Example:
from django.http import Http404
from django.shortcuts import render
def secret_page(request):
raise Http404("This page does not exist")
If this view is visited, Django will return a 404 response.
This is useful when:
get_object_or_404Django provides a shortcut called get_object_or_404 that is much cleaner than writing try/except yourself.
from django.http import Http404
from .models import Post
def post_detail(request, post_id):
try:
post = Post.objects.get(id=post_id)
except Post.DoesNotExist:
raise Http404("Post not found")
return render(request, 'blog/post_detail.html', {'post': post})from django.shortcuts import render, get_object_or_404
from .models import Post
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id)
return render(request, 'blog/post_detail.html', {'post': post})This is the preferred way in most Django projects.
You can also use it with slugs:
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'blog/post_detail.html', {'post': post})To create a custom 404 page, make a template named:
404.htmlDjango will automatically use this file when DEBUG = False.
Create this file inside your templates folder:
<!-- templates/404.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Not Found</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f8f9fa;
text-align: center;
padding: 60px;
}
.box {
max-width: 600px;
margin: auto;
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 4px 14px rgba(0,0,0,0.08);
}
h1 {
font-size: 48px;
color: #dc3545;
}
p {
font-size: 18px;
color: #555;
}
a {
display: inline-block;
margin-top: 20px;
text-decoration: none;
background: #0d6efd;
color: white;
padding: 12px 20px;
border-radius: 8px;
}
a:hover {
background: #0b5ed7;
}
</style>
</head>
<body>
<div class="box">
<h1>404</h1>
<p>Sorry, the page you are looking for does not exist.</p>
<a href="/">Go Back Home</a>
</div>
</body>
</html>This works, but in a real project it is better to extend your base template.
If your project already has a base.html, your 404.html can reuse the site layout.
Example:
{% extends 'base.html' %}
{% block title %}Page Not Found{% endblock %}
{% block content %}
<div class="container text-center py-5">
<h1 class="display-1 text-danger">404</h1>
<h2>Page Not Found</h2>
<p class="lead">Sorry, the page you requested does not exist or has been moved.</p>
<a href="{% url 'home' %}" class="btn btn-primary">Return to Home</a>
</div>
{% endblock %}This version looks more professional because it follows the same design as the rest of the site.
DEBUG = FalseYour custom 404.html only appears when:
DEBUG = False
in settings.py.
Why?
Because when DEBUG = True, Django shows its default debug page to help developers troubleshoot problems.
So during development, you may not immediately see your custom 404 page.
Your template must be inside a directory Django knows.
Example settings.py:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]If your 404.html is in:
templates/404.htmlDjango can find it.
Since the custom 404 page is shown with DEBUG = False, testing can confuse beginners.
Set:
DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']Then run the server and visit a URL that does not exist, such as:
http://127.0.0.1:8000/this-page-does-not-exist/
Django should display your custom 404.html.
You can temporarily create a view that raises Http404.
from django.http import Http404
def test_404(request):
raise Http404("Test 404 error")and add a route:
path('test-404/', views.test_404),Then visit /test-404/.
DEBUG = TrueIf DEBUG = True, Django will usually show the technical debug page instead.
If 404.html is not inside a recognized templates directory, Django will not find it.
The file must be named exactly:
404.htmlnot:
404_page.htmlerror404.htmlpage404.htmlALLOWED_HOSTSWhen DEBUG = False, Django requires ALLOWED_HOSTS to be configured.
Example:
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']Django also supports other custom error pages:
400.html → Bad Request403.html → Permission Denied404.html → Page Not Found500.html → Server ErrorYou can create all of them in your templates folder.
Example:
templates/
400.html
403.html
404.html
500.htmlThis helps make your whole site look consistent even when errors happen.
A 500 error means there is an internal server error.
You can create:
<!-- templates/500.html -->
{% extends 'base.html' %}
{% block title %}Server Error{% endblock %}
{% block content %}
<div class="container text-center py-5">
<h1 class="display-1 text-warning">500</h1>
<h2>Something went wrong</h2>
<p class="lead">Sorry, an unexpected error occurred. Please try again later.</p>
<a href="{% url 'home' %}" class="btn btn-primary">Return to Home</a>
</div>
{% endblock %}This is useful because users see a polite message instead of a broken-looking page.
urls.pyIn most simple cases, just creating 404.html is enough. But Django also allows custom error handlers.
In your main urls.py, you can define:
handler404 = 'myproject.views.custom_404'Then in views.py:
from django.shortcuts import render
def custom_404(request, exception):
return render(request, '404.html', status=404)This gives you more control. For example, you can:
Example with context:
def custom_404(request, exception):
return render(request, '404.html', {
'requested_path': request.path,
}, status=404)Then in 404.html:
<p>The page <strong>{{ requested_path }}</strong> could not be found.</p>A simple project may look like this:
myproject/
│
├── manage.py
├── myproject/
│ ├── settings.py
│ ├── urls.py
│ └── views.py
│
├── templates/
│ ├── base.html
│ ├── 404.html
│ └── 500.htmlImagine you have a blog with posts.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
def __str__(self):
return self.titlefrom django.shortcuts import render, get_object_or_404
from .models import Post
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'blog/post_detail.html', {'post': post})from django.urls import path
from . import views
urlpatterns = [
path('post/<slug:slug>/', views.post_detail, name='post_detail'),
]If the slug does not exist, Django automatically shows the 404 page.
Example:
/post/django-introduction/ → existing post → works/post/unknown-post/ → no matching post → 404 pageThis is a very common real-life use case.
A good 404 page should help the user continue browsing.
You can include:
Example:
{% extends 'base.html' %}
{% block title %}Page Not Found{% endblock %}
{% block content %}
<div class="container text-center py-5">
<h1 class="display-1 text-danger">404</h1>
<h2>Oops! This page does not exist.</h2>
<p class="lead">You may have typed the wrong address or the page may have been removed.</p>
<a href="{% url 'home' %}" class="btn btn-primary me-2">Go Home</a>
<a href="{% url 'blog_list' %}" class="btn btn-outline-secondary">Read Blog Posts</a>
</div>
{% endblock %}This is much more useful than only saying “Not found.”
Http404 vs RedirectSometimes beginners wonder whether they should show a 404 or redirect the user.
Use 404 when:
Use redirect when:
Example redirect:
from django.shortcuts import redirect
def old_page(request):
return redirect('home')So:
views.pyfrom django.shortcuts import render, get_object_or_404
from django.http import Http404
from .models import Post
def home(request):
return render(request, 'home.html')
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, 'post_detail.html', {'post': post})
def custom_404(request, exception):
return render(request, '404.html', status=404)urls.pyfrom django.contrib import admin
from django.urls import path
from . import views
handler404 = 'myproject.views.custom_404'
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.home, name='home'),
path('post/<slug:slug>/', views.post_detail, name='post_detail'),
]templates/404.html{% extends 'base.html' %}
{% block title %}404 - Page Not Found{% endblock %}
{% block content %}
<div style="text-align:center; padding:60px;">
<h1>404</h1>
<p>Sorry, the requested page could not be found.</p>
<a href="{% url 'home' %}">Back to Home</a>
</div>
{% endblock %}In Django, handling 404 errors correctly is an important part of building a professional website. A 404 error happens when a page or object cannot be found. Instead of showing users a technical default page, you should create a custom 404.html page that is clean, friendly, and helpful.
You learned that:
Http404,get_object_or_404 is the easiest way to retrieve objects safely,404.html is used when DEBUG = False,handler404,500.html, 403.html, and 400.html.A good error page is not just for errors. It is part of the overall quality of your website.
Create a small Django project and do the following:
get_object_or_404 in the detail view.404.html.