diff --git a/lib/context.c b/lib/context.c
index 11a0694d7d6f3c31c74dddefe2da9dc178d8c1d6..5054d8a5ad63dde5b451b286de2f456e2631e02f 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -583,6 +583,11 @@ lws_create_vhost(struct lws_context *context,
 	else
 		vh->name = info->vhost_name;
 
+	vh->error_document_404 = info->error_document_404;
+	if (info->error_document_404 &&
+	    info->error_document_404[0] == '/')
+		vh->error_document_404 = info->error_document_404 + 1;
+
 	if (info->options & LWS_SERVER_OPTION_ONLY_RAW)
 		lwsl_info("%s set to only support RAW\n", vh->name);
 
diff --git a/lib/header.c b/lib/header.c
index 564b0751af2399e3855d90a4cf90cd2dbc5d72b8..5022f26aea96948c96894455429cfb643b72524f 100644
--- a/lib/header.c
+++ b/lib/header.c
@@ -233,6 +233,20 @@ lws_return_http_status(struct lws *wsi, unsigned int code,
 	int n = 0, m = 0, len;
 	char slen[20];
 
+	if (wsi->vhost &&
+	    !wsi->handling_404 &&
+	    wsi->vhost->error_document_404 &&
+	    code == HTTP_STATUS_NOT_FOUND)
+		/* we should do a redirect, and do the 404 there */
+		if (lws_http_redirect(wsi, HTTP_STATUS_FOUND,
+				       (uint8_t *)wsi->vhost->error_document_404,
+				       strlen(wsi->vhost->error_document_404),
+				       &p, end) > 0)
+			return 0;
+
+	/* if the redirect failed, just do a simple status */
+	p = start;
+
 	if (!html_body)
 		html_body = "";
 
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index f3fb4598fe0e3d472943424fe4ebcf2385fdc11a..b62660d6947dd424f4372fa3add3ee519e64a17f 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -2850,6 +2850,10 @@ struct lws_context_creation_info {
 	 *	      platform default values.
 	 *	      Just leave all at 0 if you don't care.
 	 */
+	const char *error_document_404;
+	/**< VHOST: If non-NULL, when asked to serve a non-existent file,
+	 *          lws attempts to server this url path instead.  Eg,
+	 *          "/404.html" */
 
 	/* Add new things just above here ---^
 	 * This is part of the ABI, don't needlessly break compatibility
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index e4be958cb9ea952d37bf0b1525f54ea16a82680b..fcd08dc38b3f790c1ff6420b5606ff304d6cf41e 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -965,6 +965,7 @@ struct lws_vhost {
 	const struct lws_protocol_vhost_options *pvo;
 	const struct lws_protocol_vhost_options *headers;
 	struct lws **same_vh_protocol_list;
+	const char *error_document_404;
 #ifdef LWS_OPENSSL_SUPPORT
 	lws_tls_ctx *ssl_ctx;
 	lws_tls_ctx *ssl_client_ctx;
@@ -1951,6 +1952,7 @@ struct lws {
 	unsigned int rxflow_will_be_applied:1;
 	unsigned int event_pipe:1;
 	unsigned int on_same_vh_list:1;
+	unsigned int handling_404;
 
 	unsigned int could_have_pending:1; /* detect back-to-back writes */
 
diff --git a/lib/server/lejp-conf.c b/lib/server/lejp-conf.c
index da21697f76bebf5b2b17dfe7163516a57f98f002..488680cac3445658a196671eec81820859749062 100644
--- a/lib/server/lejp-conf.c
+++ b/lib/server/lejp-conf.c
@@ -101,6 +101,7 @@ static const char * const paths_vhosts[] = {
 	"vhosts[].onlyraw",
 	"vhosts[].client-cert-required",
 	"vhosts[].ignore-missing-cert",
+	"vhosts[].error-document-404",
 };
 
 enum lejp_vhost_paths {
@@ -150,6 +151,7 @@ enum lejp_vhost_paths {
 	LEJPVP_FLAG_ONLYRAW,
 	LEJPVP_FLAG_CLIENT_CERT_REQUIRED,
 	LEJPVP_IGNORE_MISSING_CERT,
+	LEJPVP_ERROR_DOCUMENT_404,
 };
 
 static const char * const parser_errs[] = {
@@ -708,6 +710,10 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
 
 		return 0;
 
+	case LEJPVP_ERROR_DOCUMENT_404:
+		a->info->error_document_404 = a->p;
+		break;
+
 	case LEJPVP_SSL_OPTION_SET:
 		a->info->ssl_options_set |= atol(ctx->buf);
 		return 0;
diff --git a/lib/server/server.c b/lib/server/server.c
index 0e3e79aff593f1fdc611dbcc5f86f58cb0d609e4..1cf60ba1e1168d420181a894b0cb7f2bbc7f5044 100644
--- a/lib/server/server.c
+++ b/lib/server/server.c
@@ -386,6 +386,11 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin,
 #endif
 	int n;
 
+	wsi->handling_404 = 0;
+	if (wsi->vhost && wsi->vhost->error_document_404 &&
+	    !strcmp(uri, wsi->vhost->error_document_404))
+		wsi->handling_404 = 1;
+
 	lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri);
 
 #if !defined(_WIN32_WCE)
@@ -2736,6 +2741,9 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
 	const struct lws_plat_file_ops *fops;
 	const char *vpath;
 
+	if (wsi->handling_404)
+		n = HTTP_STATUS_NOT_FOUND;
+
 	/*
 	 * We either call the platform fops .open with first arg platform fops,
 	 * or we call fops_zip .open with first arg platform fops, and fops_zip
diff --git a/minimal-examples/minimal-http-server/minimal-http-server.c b/minimal-examples/minimal-http-server/minimal-http-server.c
index 0e2e29e72812d1a387719f188035e7965303f110..0784b550f93fc781a9d6a00f34e08b3e4c949fc2 100644
--- a/minimal-examples/minimal-http-server/minimal-http-server.c
+++ b/minimal-examples/minimal-http-server/minimal-http-server.c
@@ -54,6 +54,7 @@ int main(int argc, char **argv)
 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
 	info.port = 7681;
 	info.mounts = &mount;
+	info.error_document_404 = "/404.html";
 
 	lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_USER
 			/* | LLL_INFO */ /* | LLL_DEBUG */, NULL);