diff --git a/lib/roles/http/private.h b/lib/roles/http/private.h
index 569991474..d54f4e44b 100644
--- a/lib/roles/http/private.h
+++ b/lib/roles/http/private.h
@@ -228,6 +228,7 @@ struct _lws_http_mode_related {
 	unsigned int perform_rewrite:1;
 #endif
 	unsigned int deferred_transaction_completed:1;
+	unsigned int content_length_explicitly_zero:1;
 };
 
 
diff --git a/lib/roles/http/server/lws-spa.c b/lib/roles/http/server/lws-spa.c
index 88675ef9a..916254c0a 100644
--- a/lib/roles/http/server/lws-spa.c
+++ b/lib/roles/http/server/lws-spa.c
@@ -572,6 +572,9 @@ lws_spa_get_string(struct lws_spa *ludspa, int n)
 LWS_VISIBLE LWS_EXTERN int
 lws_spa_finalize(struct lws_spa *spa)
 {
+	if (!spa)
+		return 0;
+
 	if (spa->s) {
 		lws_urldecode_s_destroy(spa->s);
 		spa->s = NULL;
diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c
index 4109623f2..9377f18bb 100644
--- a/lib/roles/http/server/server.c
+++ b/lib/roles/http/server/server.c
@@ -905,6 +905,7 @@ lws_http_action(struct lws *wsi)
 	/* HTTP header had a content length? */
 
 	wsi->http.rx_content_length = 0;
+	wsi->http.content_length_explicitly_zero = 0;
 	if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) ||
 		lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) ||
 		lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI))
@@ -915,6 +916,11 @@ lws_http_action(struct lws *wsi)
 			     sizeof(content_length_str) - 1,
 			     WSI_TOKEN_HTTP_CONTENT_LENGTH);
 		wsi->http.rx_content_length = atoll(content_length_str);
+		if (!wsi->http.rx_content_length) {
+			wsi->http.content_length_explicitly_zero = 1;
+			lwsl_notice("%s: explicit 0 content-length\n",
+				    __func__);
+		}
 	}
 
 	if (wsi->http2_substream) {
@@ -1345,6 +1351,34 @@ deal_body:
 		lwsl_debug("wsi->http.rx_content_length %lld %d %d\n",
 			   (long long)wsi->http.rx_content_length,
 			   wsi->upgraded_to_http2, wsi->http2_substream);
+
+		if (wsi->http.content_length_explicitly_zero &&
+		    lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) {
+
+			/*
+			 * POST with an explicit content-length of zero
+			 *
+			 * If we don't give the user code the empty HTTP_BODY
+			 * callback, he may become confused to hear the
+			 * HTTP_BODY_COMPLETION (due to, eg, instantiation of
+			 * lws_spa never happened).
+			 *
+			 * HTTP_BODY_COMPLETION is responsible for sending the
+			 * result status code and result body if any, and
+			 * do the transaction complete processing.
+			 */
+			if (wsi->protocol->callback(wsi,
+					LWS_CALLBACK_HTTP_BODY,
+					wsi->user_space, NULL, 0))
+				return 1;
+			if (wsi->protocol->callback(wsi,
+					LWS_CALLBACK_HTTP_BODY_COMPLETION,
+					wsi->user_space, NULL, 0))
+				return 1;
+
+			return 0;
+		}
+
 		if (wsi->http.rx_content_length > 0) {
 			struct lws_tokens ebuf;
 			int m;