diff --git a/component.mk b/component.mk
index 9b5235e177e6796959d317fd535d2c9e26e55721..9afdeb6f7cf713a751d571559b898daf7db4fd40 100644
--- a/component.mk
+++ b/component.mk
@@ -19,12 +19,12 @@ endif
 build:
 	cd $(COMPONENT_BUILD_DIR) ; \
 	echo "doing lws cmake" ; \
-	cmake $(COMPONENT_PATH)  -DLWS_C_FLAGS="$(CFLAGS) " \
+	cmake $(COMPONENT_PATH)  -DLWS_C_FLAGS="$(CFLAGS) -DNDEBUG=1" \
 		-DIDF_PATH=$(IDF_PATH) \
 		-DCROSS_PATH=$(CROSS_PATH) \
 		-DBUILD_DIR_BASE=$(BUILD_DIR_BASE) \
 		-DCMAKE_TOOLCHAIN_FILE=$(COMPONENT_PATH)/contrib/cross-esp32.cmake \
-		-DCMAKE_BUILD_TYPE=DEBUG \
+		-DCMAKE_BUILD_TYPE=RELEASE \
 		-DLWS_MBEDTLS_INCLUDE_DIRS="${IDF_PATH}/components/openssl/include;${IDF_PATH}/components/mbedtls/include;${IDF_PATH}/components/mbedtls/port/include" \
 		-DLWS_WITH_STATS=0 \
 		-DLWS_WITH_HTTP2=1 \
diff --git a/lib/alloc.c b/lib/alloc.c
index a7baca96452fc45d8be08830aa3db70efe29055f..e8a376675ba173b736d721cd1d4ee19576d877e2 100644
--- a/lib/alloc.c
+++ b/lib/alloc.c
@@ -52,8 +52,8 @@ static void *_realloc(void *ptr, size_t size, const char *reason)
 {
 	if (size) {
 #if defined(LWS_WITH_ESP32)
-		lwsl_notice("%s: size %lu: %s\n", __func__,
-			    (unsigned long)size, reason);
+		lwsl_notice("%s: size %lu: %s (free heap %d)\n", __func__,
+			    (unsigned long)size, reason, (unsigned int)esp_get_free_heap_size() - (int)size);
 #else
 		lwsl_debug("%s: size %lu: %s\n", __func__,
 			   (unsigned long)size, reason);
diff --git a/lib/context.c b/lib/context.c
index 57ad8b5c6635605d5bf1d0e128b829ff4441e655..3843824f0a1fd44ffd7a2c985c5efd3e321abf79 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -838,12 +838,12 @@ lws_create_vhost(struct lws_context *context,
 		vh->log_fd = (int)LWS_INVALID_FILE;
 #endif
 	if (lws_context_init_server_ssl(info, vh))
-		goto bail;
+		goto bail1;
 	if (lws_context_init_client_ssl(info, vh))
-		goto bail;
+		goto bail1;
 	if (lws_context_init_server(info, vh)) {
 		lwsl_err("init server failed\n");
-		goto bail;
+		goto bail1;
 	}
 
 	while (1) {
@@ -858,12 +858,19 @@ lws_create_vhost(struct lws_context *context,
 
 	if (context->protocol_init_done)
 		if (lws_protocol_init(context))
-			goto bail;
+			goto bail1;
 
 	return vh;
 
+bail1:
+	lws_vhost_destroy(vh);
+
+	return NULL;
+
+#ifdef LWS_WITH_ACCESS_LOG
 bail:
 	lws_free(vh);
+#endif
 
 	return NULL;
 }
diff --git a/lib/http2/http2.c b/lib/http2/http2.c
index 87cc1c17552493c13748348bbfc1b522d513ffe5..e5e5e4a89dcdb3a430b9e34695935640307c088f 100644
--- a/lib/http2/http2.c
+++ b/lib/http2/http2.c
@@ -1496,7 +1496,7 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
 			h2n->inside += n;
 			h2n->count += n - 1;
 
-			lwsl_notice("%s: count %d len %d\n", __func__, (int)h2n->count, (int)h2n->length);
+			// lwsl_notice("%s: count %d len %d\n", __func__, (int)h2n->count, (int)h2n->length);
 
 			break;
 
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 6c0e4439413c646fe95a8c2f2f5548019f57b7d9..9a35c6b98a5d20a38c10adb5709aa1565319f5be 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -571,6 +571,8 @@ struct lws_esp32 {
 	char access_pw[16];
 	char hostname[32];
 	char mac[20];
+	char le_dns[64];
+	char le_email[64];
 	mdns_server_t *mdns;
        	char region;
        	char inet;
@@ -584,6 +586,8 @@ struct lws_esp32 {
 	struct lws_group_member *first;
 	int extant_group_members;
 
+	char acme;
+
 	volatile char button_is_down;
 };
 
@@ -836,6 +840,45 @@ struct lws_ssl_info {
 	int ret;
 };
 
+enum lws_cert_update_state {
+	LWS_CUS_IDLE,
+	LWS_CUS_STARTING,
+	LWS_CUS_SUCCESS,
+	LWS_CUS_FAILED,
+
+	LWS_CUS_CREATE_KEYS,
+	LWS_CUS_REG,
+	LWS_CUS_AUTH,
+	LWS_CUS_CHALLENGE,
+	LWS_CUS_CREATE_REQ,
+	LWS_CUS_REQ,
+	LWS_CUS_CONFIRM,
+	LWS_CUS_ISSUE,
+};
+
+enum {
+	LWS_TLS_REQ_ELEMENT_COUNTRY,
+	LWS_TLS_REQ_ELEMENT_STATE,
+	LWS_TLS_REQ_ELEMENT_LOCALITY,
+	LWS_TLS_REQ_ELEMENT_ORGANIZATION,
+	LWS_TLS_REQ_ELEMENT_COMMON_NAME,
+	LWS_TLS_REQ_ELEMENT_EMAIL,
+
+	LWS_TLS_REQ_ELEMENT_COUNT,
+
+	LWS_TLS_SET_DIR_URL = LWS_TLS_REQ_ELEMENT_COUNT,
+	LWS_TLS_SET_AUTH_PATH,
+	LWS_TLS_SET_CERT_PATH,
+	LWS_TLS_SET_KEY_PATH,
+
+	LWS_TLS_TOTAL_COUNT
+};
+
+struct lws_acme_cert_aging_args {
+	struct lws_vhost *vh;
+	const char *element_overrides[LWS_TLS_TOTAL_COUNT]; /* NULL = use pvo */
+};
+
 /*
  * NOTE: These public enums are part of the abi.  If you want to add one,
  * add it at where specified so existing users are unaffected.
@@ -1352,14 +1395,25 @@ enum lws_callback_reasons {
 	/**< When a vhost TLS cert has its expiry checked, this callback
 	 * is broadcast to every protocol of every vhost in case the
 	 * protocol wants to take some action with this information.
-	 * \p in is the lws_vhost and \p len is the number of days left
-	 * before it expires, as a (ssize_t) */
+	 * \p in is a pointer to a struct lws_acme_cert_aging_args,
+	 * and \p len is the number of days left before it expires, as
+	 * a (ssize_t).  In the struct lws_acme_cert_aging_args, vh
+	 * points to the vhost the cert aging information applies to,
+	 * and element_overrides[] is an optional way to update information
+	 * from the pvos... NULL in an index means use the information from
+	 * from the pvo for the cert renewal, non-NULL in the array index
+	 * means use that pointer instead for the index. */
 	LWS_CALLBACK_TIMER					= 73,
 	/**< When the time elapsed after a call to lws_set_timer(wsi, secs)
 	 * is up, the wsi will get one of these callbacks.  The deadline
 	 * can be continuously extended into the future by later calls
 	 * to lws_set_timer() before the deadline expires, or cancelled by
 	 * lws_set_timer(wsi, -1); */
+	LWS_CALLBACK_VHOST_CERT_UPDATE				= 74,
+	/**< When a vhost TLS cert is being updated, progress is
+	 * reported to the vhost in question here, including completion
+	 * and failure.  in points to optional JSON, and len represents the
+	 * connection state using enum lws_cert_update_state */
 
 	/****** add new things just above ---^ ******/
 
@@ -5492,6 +5546,17 @@ lws_is_ssl(struct lws *wsi);
 LWS_VISIBLE LWS_EXTERN int
 lws_is_cgi(struct lws *wsi);
 
+
+struct lws_wifi_scan { /* generic wlan scan item */
+	struct lws_wifi_scan *next;
+	char ssid[32];
+	int32_t rssi; /* divide by .count to get db */
+	uint8_t bssid[6];
+	uint8_t count;
+	uint8_t channel;
+	uint8_t authmode;
+};
+
 #if defined(LWS_OPENSSL_SUPPORT) && !defined(LWS_WITH_MBEDTLS)
 /**
  * lws_get_ssl() - Return wsi's SSL context structure
@@ -5601,23 +5666,6 @@ LWS_VISIBLE LWS_EXTERN int
 lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a,
 			     const char *san_b);
 
-enum {
-	LWS_TLS_REQ_ELEMENT_COUNTRY,
-	LWS_TLS_REQ_ELEMENT_STATE,
-	LWS_TLS_REQ_ELEMENT_LOCALITY,
-	LWS_TLS_REQ_ELEMENT_ORGANIZATION,
-	LWS_TLS_REQ_ELEMENT_COMMON_NAME,
-	LWS_TLS_REQ_ELEMENT_EMAIL,
-
-	LWS_TLS_REQ_ELEMENT_COUNT,
-	LWS_TLS_SET_DIR_URL = LWS_TLS_REQ_ELEMENT_COUNT,
-	LWS_TLS_SET_AUTH_PATH,
-	LWS_TLS_SET_CERT_PATH,
-	LWS_TLS_SET_KEY_PATH,
-
-	LWS_TLS_TOTAL_COUNT
-};
-
 /**
  * lws_tls_acme_sni_csr_create() - creates a CSR and related private key PEM
  *
@@ -6614,31 +6662,31 @@ struct lejp_ctx {
 	/* arrays */
 
 	struct _lejp_stack st[LEJP_MAX_DEPTH];
-	unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */
-	unsigned short wild[LEJP_MAX_INDEX_DEPTH]; /* index array */
+	uint16_t i[LEJP_MAX_INDEX_DEPTH]; /* index array */
+	uint16_t wild[LEJP_MAX_INDEX_DEPTH]; /* index array */
 	char path[LEJP_MAX_PATH];
 	char buf[LEJP_STRING_CHUNK];
 
 	/* int */
 
-	unsigned int line;
+	uint32_t line;
 
 	/* short */
 
-	unsigned short uni;
+	uint16_t uni;
 
 	/* char */
 
-	unsigned char npos;
-	unsigned char dcount;
-	unsigned char f;
-	unsigned char sp; /* stack head */
-	unsigned char ipos; /* index stack depth */
-	unsigned char ppos;
-	unsigned char count_paths;
-	unsigned char path_match;
-	unsigned char path_match_len;
-	unsigned char wildcount;
+	uint8_t npos;
+	uint8_t dcount;
+	uint8_t f;
+	uint8_t sp; /* stack head */
+	uint8_t ipos; /* index stack depth */
+	uint8_t ppos;
+	uint8_t count_paths;
+	uint8_t path_match;
+	uint8_t path_match_len;
+	uint8_t wildcount;
 };
 
 LWS_VISIBLE LWS_EXTERN void
diff --git a/lib/plat/lws-plat-esp32.c b/lib/plat/lws-plat-esp32.c
index b57c1c1a2a4a20103ca8db9c976beb444d1cfd8c..9a58686b52f6caf6dbd73ee00b07a2ab1f7030d8 100644
--- a/lib/plat/lws-plat-esp32.c
+++ b/lib/plat/lws-plat-esp32.c
@@ -110,7 +110,7 @@ lws_poll_listen_fd(struct lws_pollfd *fd)
 
 LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line)
 {
-	printf("%d: %s", level, line);
+	lwsl_emit_stderr(level, line);
 }
 
 LWS_VISIBLE LWS_EXTERN int
@@ -709,7 +709,7 @@ static const char *gapss_str[] = {
 };
 
 static romfs_t lws_esp32_romfs;
-static TimerHandle_t leds_timer, scan_timer, debounce_timer
+static TimerHandle_t leds_timer, scan_timer, debounce_timer, association_timer
 #if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
 , mdns_timer
 #endif
@@ -811,6 +811,28 @@ static void lws_esp32_scan_timer_cb(TimerHandle_t th)
 		lwsl_err("scan start failed %d\n", n);
 }
 
+static void lws_esp32_assoc_timer_cb(TimerHandle_t th)
+{
+	int n;
+
+	xTimerStop(association_timer, 0);
+
+	if (gapss == LWS_GAPSS_STAT_HAPPY) {
+		lwsl_debug("%s: saw we were happy\n", __func__);
+
+		return;
+	}
+
+	lwsl_notice("%s: forcing rescan\n", __func__);
+
+	lws_gapss_to(LWS_GAPSS_SCAN);
+	scan_ongoing = 0;
+	n = esp_wifi_scan_start(&scan_config, false);
+	if (n != ESP_OK)
+		lwsl_err("scan start failed %d\n", n);
+}
+
+
 #if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
 
 void __attribute__(( weak ))
@@ -1018,7 +1040,7 @@ end_scan()
 		goto passthru;
 
 	if (gapss != LWS_GAPSS_SCAN) {
-		lwsl_notice("ignoring scan as gapss %s\n", gapss_str[gapss]);
+		lwsl_info("ignoring scan as gapss %s\n", gapss_str[gapss]);
 		goto passthru;
 	}
 
@@ -1029,14 +1051,14 @@ end_scan()
 	    !lws_esp32.ssid[3][0])
 		goto passthru;
 
-	lwsl_notice("checking %d scan records\n", count_ap_records);
+	lwsl_info("checking %d scan records\n", count_ap_records);
 
 	for (n = 0; n < 4; n++) {
 
 		if (!lws_esp32.ssid[(n + try_slot + 1) & 3][0])
 			continue;
 
-		lwsl_notice("looking for %s\n",
+		lwsl_debug("looking for %s\n",
 			    lws_esp32.ssid[(n + try_slot + 1) & 3]);
 
 		/* this ssid appears in scan results? */
@@ -1053,7 +1075,7 @@ end_scan()
 hit:
 		m = (n + try_slot + 1) & 3;
 		try_slot = m;
-		lwsl_notice("Attempting connection with slot %d: %s:\n", m,
+		lwsl_info("Attempting connection with slot %d: %s:\n", m,
 				lws_esp32.ssid[m]);
 		/* set the ssid we last tried to connect to */
 		strncpy(lws_esp32.active_ssid, lws_esp32.ssid[m],
@@ -1068,6 +1090,8 @@ hit:
 		tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA,
 					   (const char *)&config.ap.ssid[7]);
 		lws_gapss_to(LWS_GAPSS_STAT);
+		xTimerStop(association_timer, 0);
+		xTimerStart(association_timer, 0);
 
 		esp_wifi_set_config(WIFI_IF_STA, &sta_config);
 		esp_wifi_connect();
@@ -1484,6 +1508,8 @@ lws_esp32_wlan_config(void)
 	};
 	int n;
 
+	lwsl_debug("%s\n", __func__);
+
 	ledc_timer_config(&ledc_timer);
 
 	lws_set_genled(LWSESP32_GENLED__INIT);
@@ -1496,6 +1522,8 @@ lws_esp32_wlan_config(void)
                           (TimerCallbackFunction_t)lws_esp32_scan_timer_cb);
         debounce_timer = xTimerCreate("lws_db", pdMS_TO_TICKS(100), 0, NULL,
                           (TimerCallbackFunction_t)lws_esp32_debounce_timer_cb);
+        association_timer = xTimerCreate("lws_assoc", pdMS_TO_TICKS(10000), 0, NULL,
+                          (TimerCallbackFunction_t)lws_esp32_assoc_timer_cb);
 
 #if !defined(CONFIG_LWS_IS_FACTORY_APPLICATION)
         mdns_timer = xTimerCreate("lws_mdns", pdMS_TO_TICKS(5000), 0, NULL,
@@ -1667,13 +1695,13 @@ lws_esp32_set_creation_defaults(struct lws_context_creation_info *info)
 
 	info->vhost_name = "default";
 	info->port = 443;
-	info->fd_limit_per_thread = 30;
-	info->max_http_header_pool = 16;
-	info->max_http_header_data = 512;
-	info->pt_serv_buf_size = 2048;
+	info->fd_limit_per_thread = 16;
+	info->max_http_header_pool = 5;
+	info->max_http_header_data = 1024;
+	info->pt_serv_buf_size = 4096;
 	info->keepalive_timeout = 30;
 	info->timeout_secs = 30;
-	info->simultaneous_ssl_restriction = 4;
+	info->simultaneous_ssl_restriction = 2;
 	info->options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
 		        LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
 }
@@ -1851,6 +1879,22 @@ fail:
 	return -1;
 }
 
+void
+lws_esp32_update_acme_info(void)
+{
+        int n;
+
+	n = lws_plat_read_file("acme-email", lws_esp32.le_email,
+			       sizeof(lws_esp32.le_email) - 1);
+	if (n >= 0)
+		lws_esp32.le_email[n] = '\0';
+
+	n = lws_plat_read_file("acme-cn", lws_esp32.le_dns,
+			       sizeof(lws_esp32.le_dns) - 1);
+	if (n >= 0)
+		lws_esp32.le_dns[n] = '\0';
+}
+
 struct lws_context *
 lws_esp32_init(struct lws_context_creation_info *info, struct lws_vhost **pvh)
 {
@@ -1893,6 +1937,8 @@ lws_esp32_init(struct lws_context_creation_info *info, struct lws_vhost **pvh)
 		return NULL;
 	}
 
+	lws_esp32_update_acme_info();
+
 	lws_esp32_selfsigned(vhost);
 	wsi.context = vhost->context;
 	wsi.vhost = vhost;
@@ -1960,16 +2006,16 @@ lws_plat_write_file(const char *filename, void *buf, int len)
 
 	if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
 		lwsl_notice("%s: failed to open nvs\n", __func__);
-		return 1;
+		return -1;
 	}
 
 	n = nvs_set_blob(nvh, filename, buf, len);
-	if (n)
+	if (n >= 0)
 		nvs_commit(nvh);
 
 	nvs_close(nvh);
 
-	lwsl_notice("%s: wrote %s\n", __func__, filename);
+	lwsl_notice("%s: wrote %s (%d)\n", __func__, filename, n);
 
 	return n;
 }
@@ -1985,7 +2031,7 @@ lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf,
 	if (is_key)
 		name = vhost->key_path;
 
-	return lws_plat_write_file(name, buf, len);
+	return lws_plat_write_file(name, buf, len) < 0;
 }
 
 LWS_VISIBLE int
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 8f3374b203cd5dc4a11464ec417a56eb7d6dc8fd..6d3a1928b70f6bfb69e4da782163cacd5fff4706 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -1000,6 +1000,7 @@ struct lws_vhost {
 	unsigned int created_vhost_protocols:1;
 	unsigned int being_destroyed:1;
 	unsigned int skipped_certs:1;
+	unsigned int acme_challenge:1;
 
 	unsigned char default_protocol_index;
 	unsigned char raw_protocol_index;
diff --git a/lib/server/server.c b/lib/server/server.c
index d1f91f5f056a1de98b5549f706eefbd180d9447b..ec4cefe2b77cd9705a0cc4d0eb83cc48087a5fd6 100644
--- a/lib/server/server.c
+++ b/lib/server/server.c
@@ -2540,10 +2540,13 @@ try_pollout:
 	case LWSCM_SERVER_LISTENER:
 
 #if LWS_POSIX
-		/* pollin means a client has connected to us then */
+		/* pollin means a client has connected to us then
+		 * pollout is a hack on esp32 for background accepts signalling
+		 * they completed
+		 * */
 
 		do {
-			if (!(pollfd->revents & LWS_POLLIN) ||
+			if (!(pollfd->revents & (LWS_POLLIN |LWS_POLLOUT)) ||
 			    !(pollfd->events & LWS_POLLIN))
 				break;
 
diff --git a/lib/server/ssl-server.c b/lib/server/ssl-server.c
index ef8ba3d930510e45a3a8233f75bc75e90f0c3c9d..4e0d8b84c754525b1777f526de8cde26cb692564 100644
--- a/lib/server/ssl-server.c
+++ b/lib/server/ssl-server.c
@@ -88,9 +88,10 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info,
 
 		lws_tls_server_client_cert_verify_config(vhost);
 
-		vhost->protocols[0].callback(&wsi,
-			LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS,
-			vhost->ssl_ctx, vhost, 0);
+		if (vhost->protocols[0].callback(&wsi,
+			    LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS,
+			    vhost->ssl_ctx, vhost, 0))
+			return -1;
 	}
 
 	if (vhost->use_ssl)
@@ -142,6 +143,11 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd)
 				    context->simultaneous_ssl_restriction)
 			/* that was the last allowed SSL connection */
 			lws_gate_accepts(context, 0);
+
+		//lwsl_notice("%s: ssl restr %d, simul %d\n", __func__,
+		//		context->simultaneous_ssl_restriction,
+		//		context->simultaneous_ssl);
+
 #if defined(LWS_WITH_STATS)
 	context->updated = 1;
 #endif
diff --git a/lib/service.c b/lib/service.c
index 940ed483a945e9b6dbd17e2f83d025b3d46c398a..9dd18979c66199180f3c8022adc4fecf8d59d94e 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -1000,7 +1000,7 @@ completed:
 	if (user_callback_handle_rxflow(wsi->protocol->callback,
 			wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
 			wsi->user_space, NULL, 0)) {
-		lwsl_debug("Completed call returned -1\n");
+		lwsl_debug("%s: Completed call returned nonzero (mode %d)\n", __func__, wsi->mode);
 		return -1;
 	}
 
diff --git a/lib/tls/mbedtls/mbedtls-server.c b/lib/tls/mbedtls/mbedtls-server.c
index da3c1da59df53bd2da889efde71977a7e2f5e8ed..5e8c9b863acd504113dcc26745aff7251c170605 100644
--- a/lib/tls/mbedtls/mbedtls-server.c
+++ b/lib/tls/mbedtls/mbedtls-server.c
@@ -213,6 +213,7 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info,
 	const SSL_METHOD *method = TLS_server_method();
 	uint8_t *p;
 	lws_filepos_t flen;
+	int n;
 
 	vhost->ssl_ctx = SSL_CTX_new(method);	/* create context */
 	if (!vhost->ssl_ctx) {
@@ -243,10 +244,13 @@ lws_tls_server_vhost_backend_init(struct lws_context_creation_info *info,
 		free(p);
 	}
 
-	return lws_tls_server_certs_load(vhost, wsi,
-					 info->ssl_cert_filepath,
-					 info->ssl_private_key_filepath,
-					 NULL, 0, NULL, 0);
+	n = lws_tls_server_certs_load(vhost, wsi, info->ssl_cert_filepath,
+				      info->ssl_private_key_filepath, NULL,
+				      0, NULL, 0);
+	if (n)
+		return n;
+
+	return 0;
 }
 
 int
@@ -284,9 +288,17 @@ enum lws_ssl_capable_status
 lws_tls_server_accept(struct lws *wsi)
 {
 	union lws_tls_cert_info_results ir;
-	int m, n = SSL_accept(wsi->ssl);
+	int m, n;
 
+	n = SSL_accept(wsi->ssl);
 	if (n == 1) {
+
+		if (strstr(wsi->vhost->name, ".invalid")) {
+			lwsl_notice("%s: vhost has .invalid, rejecting accept\n", __func__);
+
+			return LWS_SSL_CAPABLE_ERROR;
+		}
+
 		n = lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME, &ir,
 					   sizeof(ir.ns.name));
 		if (!n)
@@ -298,6 +310,8 @@ lws_tls_server_accept(struct lws *wsi)
 	}
 
 	m = SSL_get_error(wsi->ssl, n);
+	lwsl_debug("%s: %p: accept SSL_get_error %d errno %d\n", __func__,
+		   wsi, m, errno);
 
 	// mbedtls wrapper only
 	if (m == SSL_ERROR_SYSCALL && errno == 11)
@@ -439,7 +453,7 @@ lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a,
 	uint8_t digest[32];
 	struct lws_genhash_ctx hash_ctx;
 	int pkey_asn1_len = 3 * 1024;
-	int n, keybits = lws_plat_recommended_rsa_bits(), adj;
+	int n, m, keybits = lws_plat_recommended_rsa_bits(), adj;
 
 	if (!buf)
 		return 1;
@@ -522,35 +536,35 @@ lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a,
 	if (!pkey_asn1)
 		goto bail2;
 
-	n = lws_genrsa_render_pkey_asn1(&ctx, 1, pkey_asn1, pkey_asn1_len);
-	if (n < 0) {
+	m = lws_genrsa_render_pkey_asn1(&ctx, 1, pkey_asn1, pkey_asn1_len);
+	if (m < 0) {
 		lws_free(pkey_asn1);
 		goto bail2;
 	}
-	lwsl_debug("private key\n");
-	lwsl_hexdump_level(LLL_DEBUG, pkey_asn1, n);
 
-	/* and to use our generated private key */
-	n = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, pkey_asn1, n);
-	lws_free(pkey_asn1);
+//	lwsl_hexdump_level(LLL_DEBUG, buf, lws_ptr_diff(p, buf));
+	n = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx,
+				 lws_ptr_diff(p, buf), buf);
 	if (n != 1) {
-		lwsl_notice("%s: SSL_CTX_use_PrivateKey_ASN1 failed\n",
-			    __func__);
+		lws_free(pkey_asn1);
+		lwsl_err("%s: generated cert failed to load 0x%x\n",
+				__func__, -n);
+	} else {
+		//lwsl_debug("private key\n");
+		//lwsl_hexdump_level(LLL_DEBUG, pkey_asn1, n);
+
+		/* and to use our generated private key */
+		n = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, pkey_asn1, m);
+		lws_free(pkey_asn1);
+		if (n != 1) {
+			lwsl_err("%s: SSL_CTX_use_PrivateKey_ASN1 failed\n",
+				    __func__);
+		}
 	}
 
 	lws_genrsa_destroy(&ctx);
 	lws_jwk_destroy_genrsa_elements(&el);
 
-	if (n == 1) {
-		lwsl_hexdump_level(LLL_DEBUG, buf, lws_ptr_diff(p, buf));
-
-		n = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx,
-					 lws_ptr_diff(p, buf), buf);
-		if (n != 1)
-			lwsl_notice("%s: generated cert failed to load 0x%x\n",
-					__func__, -n);
-	}
-
 	lws_free(buf);
 
 	return n != 1;
@@ -579,6 +593,8 @@ _rngf(void *context, unsigned char *buf, size_t len)
 	return -1;
 }
 
+static const char *x5[] = { "C", "ST", "L", "O", "CN" };
+
 /*
  * CSR is output formatted as b64url(DER)
  * Private key is output as a PEM in memory
@@ -589,9 +605,9 @@ lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[],
 			    size_t *privkey_len)
 {
 	mbedtls_x509write_csr csr;
-	char subject[200];
 	mbedtls_pk_context mpk;
 	int buf_size = 4096, n;
+	char subject[200], *p = subject, *end = p + sizeof(subject) - 1;
 	uint8_t *buf = malloc(buf_size); /* malloc because given to user code */
 
 	if (!buf)
@@ -615,13 +631,14 @@ lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[],
 
 	/* subject must be formatted like "C=TW,O=warmcat,CN=myserver" */
 
-	lws_snprintf(subject, sizeof(subject) - 1,
-		     "C=%s,ST=%s,L=%s,O=%s,CN=%s",
-		     elements[LWS_TLS_REQ_ELEMENT_COUNTRY],
-		     elements[LWS_TLS_REQ_ELEMENT_STATE],
-		     elements[LWS_TLS_REQ_ELEMENT_LOCALITY],
-		     elements[LWS_TLS_REQ_ELEMENT_ORGANIZATION],
-		     elements[LWS_TLS_REQ_ELEMENT_COMMON_NAME]);
+	for (n = 0; n < ARRAY_SIZE(x5); n++) {
+		if (p != subject)
+			*p++ = ',';
+		if (elements[n])
+			p += lws_snprintf(p, end - p, "%s=%s", x5[n],
+					  elements[n]);
+	}
+
 	if (mbedtls_x509write_csr_set_subject_name(&csr, subject))
 		goto fail1;
 
diff --git a/lib/tls/mbedtls/ssl.c b/lib/tls/mbedtls/ssl.c
index 57d75c1985b56119359cdc10337e4fa6662c854e..10c03884af665eed17eb872db98424650a378ac6 100644
--- a/lib/tls/mbedtls/ssl.c
+++ b/lib/tls/mbedtls/ssl.c
@@ -257,11 +257,16 @@ lws_ssl_close(struct lws *wsi)
 	SSL_free(wsi->ssl);
 	wsi->ssl = NULL;
 
-	if (wsi->context->simultaneous_ssl_restriction &&
+	if (!(wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) &&
+	    wsi->context->simultaneous_ssl_restriction &&
 	    wsi->context->simultaneous_ssl-- ==
 			    wsi->context->simultaneous_ssl_restriction)
 		/* we made space and can do an accept */
 		lws_gate_accepts(wsi->context, 1);
+
+	//lwsl_notice("%s: ssl restr %d, simul %d\n", __func__,
+	//		wsi->context->simultaneous_ssl_restriction,
+	//		wsi->context->simultaneous_ssl);
 #if defined(LWS_WITH_STATS)
 	wsi->context->updated = 1;
 #endif
diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
index 354b0c7f3feebf290102d6920cb686f2db063cda..1d7d0932ecc060627090a7ce6713ecf1ea38eb0c 100755
--- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
+++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c
@@ -25,6 +25,8 @@
 #include "mbedtls/error.h"
 #include "mbedtls/certs.h"
 
+#include <libwebsockets.h>
+
 #define X509_INFO_STRING_LENGTH 8192
 
 struct ssl_pm
@@ -64,7 +66,7 @@ unsigned int max_content_len;
 /*********************************************************************************************/
 /************************************ SSL arch interface *************************************/
 
-#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG
+//#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG
 
 /* mbedtls debug level */
 #define MBEDTLS_DEBUG_LEVEL 4
@@ -81,13 +83,13 @@ static void ssl_platform_debug(void *ctx, int level,
        This is a bit wasteful because the macros are compiled in with
        the full _FILE_ path in each case.
     */
-    char *file_sep = rindex(file, '/');
-    if(file_sep)
-        file = file_sep + 1;
+//    char *file_sep = rindex(file, '/');
+  //  if(file_sep)
+    //    file = file_sep + 1;
 
-    SSL_DEBUG(SSL_DEBUG_ON, "%s:%d %s", file, line, str);
+    printf("%s:%d %s", file, line, str);
 }
-#endif
+//#endif
 
 /**
  * @brief create SSL low-level object
@@ -163,12 +165,12 @@ int ssl_pm_new(SSL *ssl)
 
     mbedtls_ssl_conf_rng(&ssl_pm->conf, mbedtls_ctr_drbg_random, &ssl_pm->ctr_drbg);
 
-#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG
-    mbedtls_debug_set_threshold(MBEDTLS_DEBUG_LEVEL);
+//#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG
+ //   mbedtls_debug_set_threshold(MBEDTLS_DEBUG_LEVEL);
+//    mbedtls_ssl_conf_dbg(&ssl_pm->conf, ssl_platform_debug, NULL);
+//#else
     mbedtls_ssl_conf_dbg(&ssl_pm->conf, ssl_platform_debug, NULL);
-#else
-    mbedtls_ssl_conf_dbg(&ssl_pm->conf, NULL, NULL);
-#endif
+//#endif
 
     ret = mbedtls_ssl_setup(&ssl_pm->ssl, &ssl_pm->conf);
     if (ret) {
@@ -265,7 +267,7 @@ static int mbedtls_handshake( mbedtls_ssl_context *ssl )
     while (ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) {
         ret = mbedtls_ssl_handshake_step(ssl);
 
-        SSL_DEBUG(SSL_PLATFORM_DEBUG_LEVEL, "ssl ret %d state %d", ret, ssl->state);
+        lwsl_notice("%s: ssl ret -%x state %d\n", __func__, -ret, ssl->state);
 
         if (ret != 0)
             break;
@@ -274,14 +276,23 @@ static int mbedtls_handshake( mbedtls_ssl_context *ssl )
     return ret;
 }
 
+#include <errno.h>
+
 int ssl_pm_handshake(SSL *ssl)
 {
     int ret;
     struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
 
+    lwsl_notice("%s\n", __func__);
+
+    ssl->err = 0;
+    errno = 0;
+
     ret = ssl_pm_reload_crt(ssl);
-    if (ret)
+    if (ret) {
+	    printf("%s: cert reload failed\n", __func__);
         return 0;
+    }
 
     if (ssl_pm->ssl.state != MBEDTLS_SSL_HANDSHAKE_OVER) {
 	    ssl_speed_up_enter();
@@ -302,6 +313,7 @@ int ssl_pm_handshake(SSL *ssl)
      *   <0 = death
      */
     if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+	    ssl->err = ret;
         SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_handshake() return -0x%x", -ret);
         return 0; /* OpenSSL: did not complete but may be retried */
     }
@@ -313,6 +325,14 @@ int ssl_pm_handshake(SSL *ssl)
         return 1; /* openssl successful */
     }
 
+    if (errno == 11) {
+	    ssl->err = ret == MBEDTLS_ERR_SSL_WANT_READ;
+
+	    return 0;
+    }
+
+    printf("%s: mbedtls_ssl_handshake() returned -0x%x\n", __func__, -ret);
+
     /* it's had it */
 
     ssl->err = SSL_ERROR_SYSCALL;
@@ -377,7 +397,7 @@ int ssl_pm_read(SSL *ssl, void *buffer, int len)
 
     ret = mbedtls_ssl_read(&ssl_pm->ssl, buffer, len);
     if (ret < 0) {
-	    //printf("%s: mbedtls_ssl_read says -0x%x\n", __func__, -ret);
+	 //   lwsl_notice("%s: mbedtls_ssl_read says -0x%x\n", __func__, -ret);
         SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_read() return -0x%x", -ret);
         if (ret == MBEDTLS_ERR_NET_CONN_RESET ||
             ret <= MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE) /* fatal errors */
diff --git a/lib/tls/openssl/openssl-server.c b/lib/tls/openssl/openssl-server.c
index a3cf45acddd3d17e81fd85bde05307f34226ae24..6d2e0f190e46ececf6c135ac6fbe9801a0599dda 100644
--- a/lib/tls/openssl/openssl-server.c
+++ b/lib/tls/openssl/openssl-server.c
@@ -282,7 +282,7 @@ check_key:
 		lwsl_err("%s: no extra certs\n", __func__);
 #endif
 	if (!x) {
-		lwsl_err("%s: x is NULL\n", __func__);
+		//lwsl_err("%s: x is NULL\n", __func__);
 		goto post_ecdh;
 	}
 	/* Get the public key from certificate */
diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c
index 249a1db2f2bc105c134e0ef4411865504aa26e24..b98691f5f985dfb434e8eea8cb45f442846e992d 100644
--- a/lib/tls/openssl/ssl.c
+++ b/lib/tls/openssl/ssl.c
@@ -401,6 +401,11 @@ lws_ssl_close(struct lws *wsi)
 			    wsi->context->simultaneous_ssl_restriction)
 		/* we made space and can do an accept */
 		lws_gate_accepts(wsi->context, 1);
+
+	// lwsl_notice("%s: ssl restr %d, simul %d\n", __func__,
+	//		wsi->context->simultaneous_ssl_restriction,
+	//		wsi->context->simultaneous_ssl);
+
 #if defined(LWS_WITH_STATS)
 	wsi->context->updated = 1;
 #endif
diff --git a/lib/tls/tls.c b/lib/tls/tls.c
index 09ce781098bb2101862835d8f911dd932488b40e..d3d66707e87c598d586f688d8ae414303360dbd5 100644
--- a/lib/tls/tls.c
+++ b/lib/tls/tls.c
@@ -225,6 +225,7 @@ lws_tls_check_cert_lifetime(struct lws_vhost *v)
 {
 	union lws_tls_cert_info_results ir;
 	time_t now = (time_t)lws_now_secs(), life = 0;
+	struct lws_acme_cert_aging_args caa;
 	int n;
 
 	if (v->ssl_ctx && !v->skipped_certs) {
@@ -242,7 +243,9 @@ lws_tls_check_cert_lifetime(struct lws_vhost *v)
 	} else
 		lwsl_notice("   vhost %s: no cert\n", v->name);
 
-	lws_broadcast(v->context, LWS_CALLBACK_VHOST_CERT_AGING, v,
+	memset(&caa, 0, sizeof(caa));
+	caa.vh = v;
+	lws_broadcast(v->context, LWS_CALLBACK_VHOST_CERT_AGING, (void *)&caa,
 		      (size_t)(ssize_t)life);
 
 	return 0;
@@ -446,7 +449,7 @@ lws_gate_accepts(struct lws_context *context, int on)
 {
 	struct lws_vhost *v = context->vhost_list;
 
-	lwsl_info("gating accepts %d\n", on);
+	lwsl_notice("%s: on = %d\n", __func__, on);
 	context->ssl_gate_accepts = !on;
 #if defined(LWS_WITH_STATS)
 	context->updated = 1;
@@ -456,7 +459,7 @@ lws_gate_accepts(struct lws_context *context, int on)
 		if (v->use_ssl && v->lserv_wsi &&
 		    lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on,
 				      (LWS_POLLIN) * on))
-			lwsl_info("Unable to set accept POLLIN %d\n", on);
+			lwsl_notice("Unable to set accept POLLIN %d\n", on);
 
 		v = v->vhost_next;
 	}
diff --git a/plugins/acme-client/protocol_lws_acme_client.c b/plugins/acme-client/protocol_lws_acme_client.c
index aa0ca7980dc34cb20fc15302418c0b386667f65e..e1f333405510792eca3c4915b226264d2a2fff46 100644
--- a/plugins/acme-client/protocol_lws_acme_client.c
+++ b/plugins/acme-client/protocol_lws_acme_client.c
@@ -56,6 +56,7 @@ struct acme_connection {
 	char replay_nonce[64];
 	char chall_token[64];
 	char challenge_uri[256];
+	char detail[64];
 	char status[16];
 	char san_a[100];
 	char san_b[100];
@@ -105,6 +106,7 @@ struct per_vhost_data__lws_acme_client {
 
 	char *pvo_data;
 	char *pvop[LWS_TLS_TOTAL_COUNT];
+	const char *pvop_active[LWS_TLS_TOTAL_COUNT];
 	int count_live_pss;
 	char *dest;
 	int pos;
@@ -191,6 +193,7 @@ static const char * const jauthz_tok[] = {
 	"challenges[].status",
 	"challenges[].uri",
 	"challenges[].token",
+	"detail"
 };
 enum enum_jauthz_tok {
 	JAAZ_ID_TYPE,
@@ -201,6 +204,7 @@ enum enum_jauthz_tok {
 	JAAZ_CHALLENGES_STATUS,
 	JAAZ_CHALLENGES_URI,
 	JAAZ_CHALLENGES_TOKEN,
+	JAAZ_DETAIL,
 };
 static signed char
 cb_authz(struct lejp_ctx *ctx, char reason)
@@ -226,6 +230,9 @@ cb_authz(struct lejp_ctx *ctx, char reason)
 		break;
 	case JAAZ_EXPIRES:
 		break;
+	case JAAZ_DETAIL:
+		lws_snprintf(s->detail, sizeof(s->detail), "%s", ctx->buf);
+		break;
 	case JAAZ_CHALLENGES_TYPE:
 		if (s->is_sni_02)
 			break;
@@ -263,12 +270,14 @@ static const char * const jchac_tok[] = {
 	"status",
 	"uri",
 	"token",
+	"error.detail"
 };
 enum enum_jchac_tok {
 	JCAC_TYPE,
 	JCAC_STATUS,
 	JCAC_URI,
 	JCAC_TOKEN,
+	JCAC_DETAIL,
 };
 static signed char
 cb_chac(struct lejp_ctx *ctx, char reason)
@@ -300,6 +309,9 @@ cb_chac(struct lejp_ctx *ctx, char reason)
 				sizeof(s->chall_token) - 1);
 		s->yes |= 1;
 		break;
+	case JCAC_DETAIL:
+		lws_snprintf(s->detail, sizeof(s->detail), "%s", ctx->buf);
+		break;
 	}
 
 	return 0;
@@ -328,6 +340,14 @@ cb_chac(struct lejp_ctx *ctx, char reason)
  * (ie, just use new-cert instead of new-order, use the directory for links)
  */
 
+static int
+lws_acme_report_status(struct lws_vhost *v, int state, const char *json)
+{
+	lws_callback_vhost_protocols_vhost(v, LWS_CALLBACK_VHOST_CERT_UPDATE,
+					   (void *)json, state);
+
+	return 0;
+}
 
 /*
  * Notice: trashes i and url
@@ -339,6 +359,7 @@ lws_acme_client_connect(struct lws_context *context, struct lws_vhost *vh,
 {
 	const char *prot, *p;
 	char path[200], _url[256];
+	struct lws *wsi;
 
 	memset(i, 0, sizeof(*i));
 	i->port = 443;
@@ -364,13 +385,21 @@ lws_acme_client_connect(struct lws_context *context, struct lws_vhost *vh,
 	i->pwsi = pwsi;
 	i->protocol = "lws-acme-client";
 
-	return lws_client_connect_via_info(i);
+	wsi = lws_client_connect_via_info(i);
+	if (!wsi) {
+		lws_snprintf(path, sizeof(path) - 1,
+			     "Unable to connect to %s", url);
+		lwsl_notice("%s: %s\n", __func__, path);
+		lws_acme_report_status(vh, LWS_CUS_FAILED, path);
+	}
+
+	return wsi;
 }
 
 static void
 lws_acme_finished(struct per_vhost_data__lws_acme_client *vhd)
 {
-	lwsl_notice("finishing up jws stuff\n");
+	lwsl_debug("%s\n", __func__);
 
 	if (vhd->ac) {
 		if (vhd->ac->vhost)
@@ -384,6 +413,9 @@ lws_acme_finished(struct per_vhost_data__lws_acme_client *vhd)
 	lws_jwk_destroy(&vhd->jwk);
 
 	vhd->ac = NULL;
+#if defined(LWS_WITH_ESP32)
+	lws_esp32.acme = 0; /* enable scanning */
+#endif
 }
 
 static const char * const pvo_names[] = {
@@ -399,6 +431,113 @@ static const char * const pvo_names[] = {
 	"key-path",
 };
 
+static int
+lws_acme_load_create_auth_keys(struct per_vhost_data__lws_acme_client *vhd,
+			       int bits)
+{
+	int n;
+
+	if (!lws_jwk_load(&vhd->jwk, vhd->pvop[LWS_TLS_SET_AUTH_PATH]))
+		return 0;
+
+	strcpy(vhd->jwk.keytype, "RSA");
+	lwsl_notice("Generating ACME %d-bit keypair... "
+		    "will take a little while\n", bits);
+	n = lws_genrsa_new_keypair(vhd->context, &vhd->rsactx, &vhd->jwk.el,
+				   bits);
+	if (n) {
+		lwsl_notice("failed to create keypair\n");
+
+		return 1;
+	}
+
+	lwsl_notice("...keypair generated\n");
+
+	if (lws_jwk_save(&vhd->jwk,
+		    vhd->pvop[LWS_TLS_SET_AUTH_PATH])) {
+		lwsl_notice("unable to save %s\n",
+		      vhd->pvop[LWS_TLS_SET_AUTH_PATH]);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int
+lws_acme_start_acquisition(struct per_vhost_data__lws_acme_client *vhd,
+			   struct lws_vhost *v)
+{
+	char buf[128];
+
+	/* ...and we were given enough info to do the update? */
+
+	if (!vhd->pvop[LWS_TLS_REQ_ELEMENT_COMMON_NAME])
+		return -1;
+
+	/*
+	 * ...well... we should try to do something about it then...
+	 */
+	lwsl_notice("%s: ACME cert needs creating / updating:  "
+		    "vhost %s\n", __func__, lws_get_vhost_name(vhd->vhost));
+
+	vhd->ac = malloc(sizeof(*vhd->ac));
+	memset(vhd->ac, 0, sizeof(*vhd->ac));
+
+	/*
+	 * So if we don't have it, the first job is get the directory.
+	 *
+	 * If we already have the directory, jump straight into trying
+	 * to register our key.
+	 *
+	 * We always try to register the keys... if it's not the first
+	 * time, we will get a JSON body in the (legal, nonfatal)
+	 * response like this
+	 *
+	 * {
+	 *   "type": "urn:acme:error:malformed",
+	 *   "detail": "Registration key is already in use",
+	 *   "status": 409
+	 * }
+	 */
+	if (!vhd->ac->urls[0][0]) {
+		vhd->ac->state = ACME_STATE_DIRECTORY;
+		lws_snprintf(buf, sizeof(buf) - 1, "%s",
+			     vhd->pvop_active[LWS_TLS_SET_DIR_URL]);
+	} else {
+		vhd->ac->state = ACME_STATE_NEW_REG;
+		lws_snprintf(buf, sizeof(buf) - 1, "%s",
+			     vhd->ac->urls[JAD_NEW_REG_URL]);
+	}
+
+	vhd->ac->real_vh_port = lws_get_vhost_port(vhd->vhost);
+	vhd->ac->real_vh_name = lws_get_vhost_name(vhd->vhost);
+	vhd->ac->real_vh_iface = lws_get_vhost_iface(vhd->vhost);
+
+	lws_acme_report_status(vhd->vhost, LWS_CUS_STARTING, NULL);
+
+#if defined(LWS_WITH_ESP32)
+	lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS,
+			       "Generating keys, please wait");
+	if (lws_acme_load_create_auth_keys(vhd, 2048))
+		goto bail;
+	lws_acme_report_status(vhd->vhost, LWS_CUS_CREATE_KEYS,
+			       "Auth keys created");
+#endif
+
+	if (lws_acme_client_connect(vhd->context, vhd->vhost,
+				    &vhd->ac->cwsi, &vhd->ac->i, buf, "GET"))
+		return 0;
+
+#if defined(LWS_WITH_ESP32)
+bail:
+#endif
+	free(vhd->ac);
+	vhd->ac = NULL;
+
+	return 1;
+}
+
 static int
 callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 		     void *user, void *in, size_t len)
@@ -408,10 +547,11 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
 					lws_get_protocol(wsi));
 	char buf[LWS_PRE + 2536], *start = buf + LWS_PRE, *p = start,
-	     *end = buf + sizeof(buf) - 1, digest[32];
-	unsigned char **pp = (unsigned char **)in, *pend = in + len;
-	const char *content_type = "application/jose+json";
+	     *end = buf + sizeof(buf) - 1, digest[32], *failreason = NULL;
+	unsigned char **pp, *pend;
+	const char *content_type;
 	const struct lws_protocol_vhost_options *pvo;
+	struct lws_acme_cert_aging_args *caa;
 	struct acme_connection *ac = NULL;
 	struct lws_genhash_ctx hctx;
 	struct lws *cwsi;
@@ -420,7 +560,7 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 	if (vhd)
 		ac = vhd->ac;
 
-	switch (reason) {
+	switch ((int)reason) {
 	case LWS_CALLBACK_PROTOCOL_INIT:
 		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
 				lws_get_protocol(wsi),
@@ -461,8 +601,9 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 						pvo_names[m]);
 				n |= 1;
 			} else
-				lwsl_info("  %s: %s\n", pvo_names[m],
-					  vhd->pvop[m]);
+				if (vhd->pvop[m])
+					lwsl_info("  %s: %s\n", pvo_names[m],
+							vhd->pvop[m]);
 		if (n) {
 			free(vhd->pvo_data);
 			vhd->pvo_data = NULL;
@@ -470,30 +611,14 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 			return -1;
 		}
 
+#if !defined(LWS_WITH_ESP32)
 		/*
 		 * load (or create) the registration keypair while we
 		 * still have root
 		 */
-		if (lws_jwk_load(&vhd->jwk,
-				vhd->pvop[LWS_TLS_SET_AUTH_PATH])) {
-			strcpy(vhd->jwk.keytype, "RSA");
-			n = lws_genrsa_new_keypair(lws_get_context(wsi),
-						   &vhd->rsactx, &vhd->jwk.el,
-						   4096);
-			if (n) {
-				lwsl_notice("failed to create keypair\n");
-
-				return 1;
-			}
-
-			if (lws_jwk_save(&vhd->jwk,
-				    vhd->pvop[LWS_TLS_SET_AUTH_PATH])) {
-				lwsl_notice("unable to save %s\n",
-				      vhd->pvop[LWS_TLS_SET_AUTH_PATH]);
+		if (lws_acme_load_create_auth_keys(vhd, 4096))
+			return 1;
 
-				return 1;
-			}
-		}
 		/*
 		 * in case we do an update, open the update files while we
 		 * still have root
@@ -514,6 +639,7 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 			lwsl_err("unable to create update key file %s\n", buf);
 			return -1;
 		}
+#endif
 		break;
 
 	case LWS_CALLBACK_PROTOCOL_DESTROY:
@@ -528,6 +654,8 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 	case LWS_CALLBACK_VHOST_CERT_AGING:
 		if (!vhd)
 			break;
+
+		caa = (struct lws_acme_cert_aging_args *)in;
 		/*
 		 * Somebody is telling us about a cert some vhost is using.
 		 *
@@ -536,64 +664,23 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 		 */
 		if ((int)(ssize_t)len > 14)
 			break;
-		/*
-		 * ...is this a vhost we were configured on?
-		 */
-		if (vhd->vhost != (struct lws_vhost *)in)
-			break;
-
-		/* ...and we were given enough info to do the update? */
-
-		if (!vhd->pvop[LWS_TLS_REQ_ELEMENT_COUNTRY])
-			break;
 
 		/*
-		 * ...well... we should try to do something about it then...
+		 * ...is this a vhost we were configured on?
 		 */
-		lwsl_notice("%s: ACME cert needs updating:  "
-			    "vhost %s: %dd left\n", __func__,
-			    lws_get_vhost_name(in), (int)(ssize_t)len);
+		if (vhd->vhost != caa->vh)
+			return 1;
 
-		vhd->ac = ac = malloc(sizeof(*vhd->ac));
-		memset(ac, 0, sizeof(*ac));
+		for (n = 0; n < (int)ARRAY_SIZE(vhd->pvop);n++)
+			if (caa->element_overrides[n])
+				vhd->pvop_active[n] = caa->element_overrides[n];
+			else
+				vhd->pvop_active[n] = vhd->pvop[n];
 
-		/*
-		 * So if we don't have it, the first job is get the directory.
-		 *
-		 * If we already have the directory, jump straight into trying
-		 * to register our key.
-		 *
-		 * We always try to register the keys... if it's not the first
-		 * time, we will get a JSON body in the (legal, nonfatal)
-		 * response like this
-		 *
-		 * {
-		 *   "type": "urn:acme:error:malformed",
-		 *   "detail": "Registration key is already in use",
-		 *   "status": 409
-		 * }
-		 */
-		if (!ac->urls[0][0]) {
-			ac->state = ACME_STATE_DIRECTORY;
-			lws_snprintf(buf, sizeof(buf) - 1, "%s",
-				     vhd->pvop[LWS_TLS_SET_DIR_URL]);
-		} else {
-			ac->state = ACME_STATE_NEW_REG;
-			lws_snprintf(buf, sizeof(buf) - 1, "%s",
-				     ac->urls[JAD_NEW_REG_URL]);
-		}
-
-		ac->real_vh_port = lws_get_vhost_port((struct lws_vhost *)in);
-		ac->real_vh_name = lws_get_vhost_name((struct lws_vhost *)in);
-		ac->real_vh_iface = lws_get_vhost_iface((struct lws_vhost *)in);
+		lwsl_notice("starting acme acquisition on %s: %s\n",
+				lws_get_vhost_name(caa->vh), vhd->pvop_active[LWS_TLS_SET_DIR_URL]);
 
-		cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
-					       &ac->cwsi, &ac->i, buf, "GET");
-		if (!cwsi) {
-			lwsl_notice("%s: acme connect failed\n", __func__);
-			free(vhd->ac);
-			vhd->ac = NULL;
-		}
+		lws_acme_start_acquisition(vhd, caa->vh);
 		break;
 
 	/*
@@ -605,11 +692,15 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 		break;
 
 	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
-		lwsl_notice("%s: CLIENT_CONNECTION_ERROR\n", __func__);
+		lwsl_notice("%s: CLIENT_CONNECTION_ERROR: %p\n", __func__, wsi);
 		break;
 
 	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
-		lwsl_notice("%s: CLOSED_CLIENT_HTTP\n", __func__);
+		lwsl_notice("%s: CLOSED_CLIENT_HTTP: %p\n", __func__, wsi);
+		break;
+
+	case LWS_CALLBACK_CLOSED:
+		lwsl_notice("%s: CLOSED: %p\n", __func__, wsi);
 		break;
 
 	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
@@ -625,7 +716,7 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 				 WSI_TOKEN_REPLAY_NONCE) < 0) {
 			lwsl_notice("%s: nonce too large\n", __func__);
 
-			return -1;
+			goto failed;
 		}
 
 		switch (ac->state) {
@@ -687,8 +778,10 @@ callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason,
 					  "\"mailto:%s\""
 					  "],\"agreement\":\"%s\""
 					  "}",
-					  vhd->pvop[LWS_TLS_REQ_ELEMENT_EMAIL],
+					  vhd->pvop_active[LWS_TLS_REQ_ELEMENT_EMAIL],
 					  ac->urls[JAD_TOS_URL]);
+
+			puts(start);
 pkt_add_hdrs:
 			ac->len = lws_jws_create_packet(&vhd->jwk,
 							start, p - start,
@@ -701,23 +794,33 @@ pkt_add_hdrs:
 				lwsl_notice("lws_jws_create_packet failed\n");
 				goto failed;
 			}
+
+			pp = (unsigned char **)in;
+			pend = (*pp) + len;
+
 			ac->pos = 0;
+			content_type =         "application/jose+json";
 			if (ac->state == ACME_STATE_POLLING_CSR)
 				content_type = "application/pkix-cert";
 
 			if (lws_add_http_header_by_token(wsi,
 				    WSI_TOKEN_HTTP_CONTENT_TYPE,
-					(uint8_t *)content_type, 21, pp, pend))
+					(uint8_t *)content_type, 21, pp, pend)) {
+				lwsl_notice("could not add content type\n");
 				goto failed;
+			}
 
 			n = sprintf(buf, "%d", ac->len);
 			if (lws_add_http_header_by_token(wsi,
 					WSI_TOKEN_HTTP_CONTENT_LENGTH,
-					(uint8_t *)buf, n, pp, pend))
+					(uint8_t *)buf, n, pp, pend)) {
+				lwsl_notice("could not add content length\n");
 				goto failed;
+			}
 
 			lws_client_http_body_pending(wsi, 1);
 			lws_callback_on_writable(wsi);
+			lwsl_notice("prepare to send ACME_STATE_NEW_REG\n");
 			break;
 		case ACME_STATE_NEW_AUTH:
 			p += lws_snprintf(p, end - p,
@@ -727,7 +830,7 @@ pkt_add_hdrs:
 					  "\"type\":\"http-01\","
 					  "\"value\":\"%s\""
 					 "}"
-					"}", ac->real_vh_name);
+					"}", vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME]);
 			goto pkt_add_hdrs;
 
 		case ACME_STATE_ACCEPT_CHALL:
@@ -842,8 +945,7 @@ pkt_add_hdrs:
 					  "{\"resource\":\"new-cert\","
 					  "\"csr\":\"");
 			n = lws_tls_acme_sni_csr_create(vhd->context,
-							(const char **)
-								vhd->pvop,
+							&vhd->pvop_active[0],
 							(uint8_t *)p, end - p,
 							&ac->alloc_privkey_pem,
 							&ac->len_privkey_pem);
@@ -948,15 +1050,18 @@ pkt_add_hdrs:
 			 * not complete for some reason...
 			 */
 			ac->state = ACME_STATE_NEW_REG;
+			lws_acme_report_status(vhd->vhost, LWS_CUS_REG, NULL);
 
 			strcpy(buf, ac->urls[JAD_NEW_REG_URL]);
 			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
 						       &ac->cwsi, &ac->i, buf,
 						       "POST");
-			if (!cwsi)
+			if (!cwsi) {
 				lwsl_notice("%s: failed to connect to acme\n",
 					    __func__);
-			break;
+				goto failed;
+			}
+			return -1; /* close the completed client connection */
 
 		case ACME_STATE_NEW_REG:
 			if ((ac->resp >= 200 && ac->resp < 299) ||
@@ -967,6 +1072,8 @@ pkt_add_hdrs:
 				 * Move on to requesting a cert auth.
 				 */
 				ac->state = ACME_STATE_NEW_AUTH;
+				lws_acme_report_status(vhd->vhost, LWS_CUS_AUTH,
+							NULL);
 
 				strcpy(buf, ac->urls[JAD_NEW_AUTHZ_URL]);
 				cwsi = lws_acme_client_connect(vhd->context,
@@ -975,16 +1082,32 @@ pkt_add_hdrs:
 				if (!cwsi)
 					lwsl_notice("%s: failed to connect\n",
 						    __func__);
-				break;
+				return -1; /* close the completed client connection */
 			} else {
 				lwsl_notice("new-reg replied %d\n", ac->resp);
 				goto failed;
 			}
-			break;
+			return -1; /* close the completed client connection */
 
 		case ACME_STATE_NEW_AUTH:
 			lejp_destruct(&ac->jctx);
-			lwsl_notice("chall: %s\n", ac->chall_token);
+			if (ac->resp / 100 == 4) {
+				lws_snprintf(buf, sizeof(buf),
+					     "Auth failed: %s", ac->detail);
+				failreason = buf;
+				lwsl_notice("auth failed\n");
+				goto failed;
+			}
+			lwsl_notice("chall: %s (%d)\n", ac->chall_token, ac->resp);
+			if (!ac->chall_token[0]) {
+				lwsl_notice("no challenge\n");
+				goto failed;
+			}
+
+
+			ac->state = ACME_STATE_ACCEPT_CHALL;
+			lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE,
+						NULL);
 
 			/* tls-sni-01 ... what a mess.
 			 * The stuff in
@@ -1141,7 +1264,6 @@ pkt_add_hdrs:
 			 * server know we are ready to roll...
 			 */
 
-			ac->state = ACME_STATE_ACCEPT_CHALL;
 			ac->goes_around = 0;
 			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
 						       &ac->cwsi, &ac->i,
@@ -1152,7 +1274,7 @@ pkt_add_hdrs:
 					    __func__);
 				goto failed;
 			}
-			break;
+			return -1; /* close the completed client connection */
 
 		case ACME_STATE_ACCEPT_CHALL:
 			/*
@@ -1176,23 +1298,18 @@ pkt_add_hdrs:
 					__func__, ac->challenge_uri);
 poll_again:
 			ac->state = ACME_STATE_POLLING;
+			lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, NULL);
 
-			if (ac->goes_around++ == 10) {
+			if (ac->goes_around++ == 20) {
 				lwsl_notice("%s: too many chall retries\n",
 					    __func__);
 
 				goto failed;
 			}
-			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
-						       &ac->cwsi, &ac->i,
-						       ac->challenge_uri,
-						       "GET");
-			if (!cwsi) {
-				lwsl_notice("%s: failed to connect\n",
-					    __func__);
-				goto failed;
-			}
-			break;
+
+			lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol,
+					LWS_CALLBACK_USER + 0xac33, ac->goes_around == 1 ? 10 : 2);
+			return -1; /* close the completed client connection */
 
 		case ACME_STATE_POLLING:
 
@@ -1205,13 +1322,16 @@ poll_again:
 
 			if (!strcmp(ac->status, "invalid")) {
 				lwsl_notice("%s: polling failed\n", __func__);
+				lws_snprintf(buf, sizeof(buf),
+					     "Challenge Invalid: %s", ac->detail);
+				failreason = buf;
 				goto failed;
 			}
 
-			lwsl_notice("Authorization accepted\n");
+			lwsl_notice("Challenge passed\n");
 
 			/*
-			 * our authorization was validated... so delete the
+			 * The challenge was validated... so delete the
 			 * temp SNI vhost now its job is done
 			 */
 			if (ac->vhost)
@@ -1225,6 +1345,7 @@ poll_again:
 			 * server to request the actual certs.
 			 */
 			ac->state = ACME_STATE_POLLING_CSR;
+			lws_acme_report_status(vhd->vhost, LWS_CUS_REQ, NULL);
 			ac->goes_around = 0;
 
 			strcpy(buf, ac->urls[JAD_NEW_CERT_URL]);
@@ -1237,7 +1358,7 @@ poll_again:
 
 				goto failed;
 			}
-			break;
+			return -1; /* close the completed client connection */
 
 		case ACME_STATE_POLLING_CSR:
 			/*
@@ -1273,6 +1394,10 @@ poll_again:
 				int max;
 
 				lwsl_notice("The cert was sent..\n");
+
+				lws_acme_report_status(vhd->vhost,
+						LWS_CUS_ISSUE, NULL);
+
 				/*
 				 * That means we have the issued cert DER in
 				 * ac->buf, length in ac->cpos; and the key in
@@ -1334,7 +1459,7 @@ poll_again:
 						lws_ptr_diff(p, start));
 				free(start);
 				if (n) {
-					lwsl_err("unable to write ACME cert!\n");
+					lwsl_err("unable to write ACME cert! %d\n", n);
 					goto failed;
 				}
 				/*
@@ -1356,15 +1481,15 @@ poll_again:
 
 				lwsl_notice("%s: Updated certs written for %s "
 					    "to %s.upd and %s.upd\n", __func__,
-					    vhd->pvop[LWS_TLS_REQ_ELEMENT_COMMON_NAME],
-					    vhd->pvop[LWS_TLS_SET_CERT_PATH],
-					    vhd->pvop[LWS_TLS_SET_KEY_PATH]);
+					    vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME],
+					    vhd->pvop_active[LWS_TLS_SET_CERT_PATH],
+					    vhd->pvop_active[LWS_TLS_SET_KEY_PATH]);
 
 				/* notify lws there was a cert update */
 
 				if (lws_tls_cert_updated(vhd->context,
-					vhd->pvop[LWS_TLS_SET_CERT_PATH],
-					vhd->pvop[LWS_TLS_SET_KEY_PATH],
+					vhd->pvop_active[LWS_TLS_SET_CERT_PATH],
+					vhd->pvop_active[LWS_TLS_SET_KEY_PATH],
 					ac->buf, ac->cpos,
 					ac->alloc_privkey_pem,
 					ac->len_privkey_pem)) {
@@ -1372,10 +1497,14 @@ poll_again:
 				}
 
 				lws_acme_finished(vhd);
+				lws_acme_report_status(vhd->vhost,
+							LWS_CUS_SUCCESS, NULL);
 
 				return 0;
 			}
 
+			lws_acme_report_status(vhd->vhost, LWS_CUS_CONFIRM, NULL);
+
 			/* he is preparing the cert, go again with a GET */
 
 			if (ac->goes_around++ == 30) {
@@ -1395,13 +1524,24 @@ poll_again:
 
 				goto failed;
 			}
-			break;
+			return -1; /* close the completed client connection */
 
 		default:
 			break;
 		}
 		break;
 
+		case LWS_CALLBACK_USER + 0xac33:
+			cwsi = lws_acme_client_connect(vhd->context, vhd->vhost,
+						       &ac->cwsi, &ac->i,
+						       ac->challenge_uri,
+						       "GET");
+			if (!cwsi) {
+				lwsl_notice("%s: failed to connect\n", __func__);
+				goto failed;
+			}
+			break;
+
 	case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
 		/*
 		 * This goes to vhost->protocols[0], but for our temp certs
@@ -1414,9 +1554,17 @@ poll_again:
 		lwsl_debug("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS\n");
 		ac = (struct acme_connection *)lws_get_vhost_user(
 							(struct lws_vhost *)in);
+
+		lws_acme_report_status((struct lws_vhost *)in,
+				        LWS_CUS_CREATE_REQ,
+				        "creating challenge cert");
+
 		if (lws_tls_acme_sni_cert_create((struct lws_vhost *)in,
-						 ac->san_a, ac->san_b))
+						 ac->san_a, ac->san_b)) {
+			lwsl_err("%s: creating the sni test cert failed\n", __func__);
+
 			return -1;
+		}
 		break;
 
 	default:
@@ -1427,6 +1575,7 @@ poll_again:
 
 failed:
 	lwsl_err("%s: failed out\n", __func__);
+	lws_acme_report_status(vhd->vhost, LWS_CUS_FAILED, failreason);
 	lws_acme_finished(vhd);
 
 	return -1;
diff --git a/plugins/generic-sessions/handlers.c b/plugins/generic-sessions/handlers.c
index 8b8665f6c92f1bfa1661bc939a7c4b1fb691f132..f57f44e6faf3d764267ff0e9c6a061191793cfb1 100644
--- a/plugins/generic-sessions/handlers.c
+++ b/plugins/generic-sessions/handlers.c
@@ -72,7 +72,8 @@ lwsgs_handler_confirm(struct per_vhost_data__gs *vhd, struct lws *wsi,
 	a.event = LWSGSE_CREATED;
 	a.username = u.username;
 	a.email = u.email;
-	lws_callback_vhost_protocols(wsi, LWS_CALLBACK_GS_EVENT, &a, 0);
+	lws_callback_vhost_protocols_vhost(lws_get_vhost(wsi),
+					   LWS_CALLBACK_GS_EVENT, &a, 0);
 
 	lws_snprintf(pss->onward, sizeof(pss->onward),
 		 "%s/post-verify-ok.html", vhd->email_confirm_url);
@@ -325,7 +326,8 @@ lwsgs_handler_change_password(struct per_vhost_data__gs *vhd, struct lws *wsi,
 		a.event = LWSGSE_DELETED;
 		a.username = u.username;
 		a.email = "";
-		lws_callback_vhost_protocols(wsi, LWS_CALLBACK_GS_EVENT, &a, 0);
+		lws_callback_vhost_protocols_vhost(lws_get_vhost(wsi),
+						   LWS_CALLBACK_GS_EVENT, &a, 0);
 
 		lws_snprintf(s, sizeof(s) - 1,
 			 "delete from users where username='%s';"
diff --git a/plugins/generic-sessions/protocol_lws_messageboard.c b/plugins/generic-sessions/protocol_lws_messageboard.c
index f8aef73379c85fcea0716559857bd9e7395198eb..9cc4cb26312a7305c6a05c3c58daca72a820f7c5 100644
--- a/plugins/generic-sessions/protocol_lws_messageboard.c
+++ b/plugins/generic-sessions/protocol_lws_messageboard.c
@@ -213,7 +213,7 @@ callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
 		break;
 
 	case LWS_CALLBACK_PROTOCOL_DESTROY:
-		if (vhd->pdb)
+		if (vhd && vhd->pdb)
 			sqlite3_close(vhd->pdb);
 		goto passthru;
 
diff --git a/plugins/protocol_esp32_lws_ota.c b/plugins/protocol_esp32_lws_ota.c
index cfb5748b17b3545ad3223c1c988a0bc1c99391ef..1fd80af5d0c44bfe16869c3d370fb8a8e3cc55df 100644
--- a/plugins/protocol_esp32_lws_ota.c
+++ b/plugins/protocol_esp32_lws_ota.c
@@ -33,6 +33,7 @@ struct per_session_data__esplws_ota {
 	esp_ota_handle_t otahandle;
 	const esp_partition_t *part;
 	long file_length;
+	long last_rep;
 	nvs_handle nvh;
 	TimerHandle_t reboot_timer;
 };
@@ -117,6 +118,7 @@ ota_file_upload_cb(void *data, const char *name, const char *filename,
 		}
 
 		pss->file_length = 0;
+		pss->last_rep = -1;
 		break;
 
 	case LWS_UFS_FINAL_CONTENT:
@@ -126,9 +128,11 @@ ota_file_upload_cb(void *data, const char *name, const char *filename,
 			return 1;
 		}
 
-		lwsl_notice("writing 0x%lx... 0x%lx\n",
-			   pss->part->address + pss->file_length,
-			   pss->part->address + pss->file_length + len);
+		if ((pss->file_length & ~0xffff) != (pss->last_rep & ~0xffff)) {
+			lwsl_notice("writing 0x%lx...\n",
+					pss->part->address + pss->file_length);
+			pss->last_rep = pss->file_length;
+		}
 		if (esp_ota_write(pss->otahandle, buf, len) != ESP_OK) {
 			lwsl_err("OTA: Failed to write\n");
 			return 1;
diff --git a/plugins/protocol_esp32_lws_scan.c b/plugins/protocol_esp32_lws_scan.c
index 0030700def38043a483af917f69339f7dff869d8..7537bd66f81110223a80b2103352140f81904ab0 100644
--- a/plugins/protocol_esp32_lws_scan.c
+++ b/plugins/protocol_esp32_lws_scan.c
@@ -56,6 +56,8 @@ struct per_session_data__esplws_scan {
 	unsigned char changed_partway:1;
 };
 
+#define max_aps 12
+
 struct per_vhost_data__esplws_scan {
 	wifi_ap_record_t ap_records[10];
 	TimerHandle_t timer, reboot_timer;
@@ -63,6 +65,7 @@ struct per_vhost_data__esplws_scan {
 	struct lws_context *context;
 	struct lws_vhost *vhost;
 	const struct lws_protocols *protocol;
+	struct lws_wifi_scan *known_aps_list;
 
 	const esp_partition_t *part;
 	esp_ota_handle_t otahandle;
@@ -75,6 +78,9 @@ struct per_vhost_data__esplws_scan {
 	char json[2048];
 	int json_len;
 
+	int acme_state;
+	char acme_msg[256];
+
 	uint16_t count_ap_records;
 	char count_live_pss;
 	unsigned char scan_ongoing:1;
@@ -117,6 +123,9 @@ static const char * const param_names[] = {
 	"pri",
 	"serial",
 	"opts",
+	"group",
+	"role",
+	"updsettings",
 };
 
 enum enum_param_names {
@@ -125,6 +134,9 @@ enum enum_param_names {
 	EPN_PRI,
 	EPN_SERIAL,
 	EPN_OPTS,
+	EPN_GROUP,
+	EPN_ROLE,
+	EPN_UPDSETTINGS,
 };
 
 
@@ -159,6 +171,9 @@ scan_start(struct per_vhost_data__esplws_scan *vhd)
 	if (vhd->scan_ongoing)
 		return;
 
+	if (lws_esp32.acme)
+		return;
+
 	vhd->scan_ongoing = 1;
 	lws_esp32.scan_consumer = scan_finished;
 	lws_esp32.scan_consumer_arg = vhd;
@@ -167,10 +182,17 @@ scan_start(struct per_vhost_data__esplws_scan *vhd)
 		lwsl_err("scan start failed %d\n", n);
 }
 
+static char scan_defer;
+
 static void timer_cb(TimerHandle_t t)
 {
 	struct per_vhost_data__esplws_scan *vhd = pvTimerGetTimerID(t);
 
+	if (!lws_esp32.inet && (scan_defer & 1)) {
+		/* if connected in AP mode, wait twice as long between scans */
+		return;
+	}
+
 	scan_start(vhd);
 }
 
@@ -214,11 +236,101 @@ client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
 	return 0; /* ongoing */
 }
 
+static int
+lws_wifi_scan_rssi(struct lws_wifi_scan *p)
+{
+	if (!p->count)
+		return -127;
+
+	return p->rssi / p->count;
+}
+
+/*
+ * Insert new lws_wifi_scan into linkedlist in rssi-sorted order, trimming the
+ * list if needed to keep it at or below max_aps entries.
+ */
+
+static int
+lws_wifi_scan_insert_trim(struct lws_wifi_scan **list, struct lws_wifi_scan *ns)
+{
+	int count = 0, ins = 1, worst;
+	struct lws_wifi_scan *newlist, **pworst, *pp1;
+
+	lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
+		/* try to find existing match */
+		if (!strcmp((*pp)->ssid, ns->ssid) &&
+		    !memcmp((*pp)->bssid, ns->bssid, 6)) {
+			if ((*pp)->count > 127) {
+				(*pp)->count /= 2;
+				(*pp)->rssi /= 2;
+			}
+			(*pp)->rssi += ns->rssi;
+			(*pp)->count++;
+			ins = 0;
+			break;
+		}
+	} lws_end_foreach_llp(pp, next);
+
+	if (ins) {
+		lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
+			/* trim any excess guys */
+			if (count++ >= max_aps - 1) {
+				pp1 = *pp;
+				*pp = (*pp)->next;
+				free(pp1);
+				continue; /* stay where we are */
+			}
+		} lws_end_foreach_llp(pp, next);
+
+		/* we are inserting... so alloc a copy of him */
+		pp1 = malloc(sizeof(*pp1));
+		if (!pp1)
+			return -1;
+
+		memcpy(pp1, ns, sizeof(*pp1));
+		pp1->next = *list;
+		*list = pp1;
+	}
+
+	/* sort the list ... worst first, but added at the newlist head */
+
+	newlist = NULL;
+
+	/* while anybody left on the old list */
+	while (*list) {
+		worst = 0;
+		pworst = NULL;
+
+		/* who is the worst guy still left on the old list? */
+		lws_start_foreach_llp(struct lws_wifi_scan **, pp, *list) {
+			if (lws_wifi_scan_rssi(*pp) <= worst) {
+				worst = lws_wifi_scan_rssi(*pp);
+				pworst = pp;
+			}
+		} lws_end_foreach_llp(pp, next);
+
+		if (pworst) {
+			/* move the worst to the head of the new list */
+			pp1 = *pworst;
+			*pworst = (*pworst)->next;
+			pp1->next = newlist;
+			newlist = pp1;
+		}
+	}
+
+	*list = newlist;
+
+	return 0;
+}
+
 static void
 scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v)
 {
 	struct per_vhost_data__esplws_scan *vhd = v;
 	struct per_session_data__esplws_scan *p = vhd->live_pss_list;
+	struct lws_wifi_scan lws;
+	wifi_ap_record_t *r;
+	int m;
 
 	lwsl_notice("%s: count %d\n", __func__, count);
 
@@ -232,13 +344,31 @@ scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v)
 	memcpy(vhd->ap_records, recs, vhd->count_ap_records * sizeof(*recs));
 	
 	while (p) {
-		if (p->scan_state != SCAN_STATE_INITIAL && p->scan_state != SCAN_STATE_NONE)
+		if (p->scan_state != SCAN_STATE_INITIAL &&
+		    p->scan_state != SCAN_STATE_NONE)
 			p->changed_partway = 1;
 		else
 			p->scan_state = SCAN_STATE_INITIAL;
 		p = p->next;
 	}
 
+	/* convert to generic, cumulative scan results */
+
+	for (m = 0; m < vhd->count_ap_records; m++) {
+
+		r = &vhd->ap_records[m];
+
+		lws.authmode = r->authmode;
+		lws.channel = r->primary;
+		lws.rssi = r->rssi;
+		lws.count = 1;
+		memcpy(&lws.bssid, r->bssid, 6);
+		strncpy(lws.ssid, (const char *)r->ssid, sizeof(lws.ssid) - 1);
+		lws.ssid[sizeof(lws.ssid) - 1] = '\0';
+
+		lws_wifi_scan_insert_trim(&vhd->known_aps_list, &lws);
+	}
+
 	lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
 
 	if (lws_esp32.inet && !vhd->cwsi && !vhd->checked_updates)
@@ -320,9 +450,8 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 	unsigned char *start = pss->buffer + LWS_PRE - 1, *p = start,
 		      *end = pss->buffer + sizeof(pss->buffer) - 1;
 	union lws_tls_cert_info_results ir;
+	struct lws_wifi_scan *lwscan;
 	char subject[64];
-	const char *pp;
-	wifi_ap_record_t *r;
 	int n, m;
 	nvs_handle nvh;
 	size_t s;
@@ -341,7 +470,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 			  (TimerCallbackFunction_t)timer_cb);
 		vhd->scan_ongoing = 0;
 		strcpy(vhd->json, " { }");
-		scan_start(vhd);
+	//	scan_start(vhd);
 		break;
 
 	case LWS_CALLBACK_PROTOCOL_DESTROY:
@@ -354,17 +483,17 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 	case LWS_CALLBACK_ESTABLISHED:
 		lwsl_notice("%s: ESTABLISHED\n", __func__);
 		if (!vhd->live_pss_list) {
-			scan_start(vhd);
+		//	scan_start(vhd);
 			xTimerStart(vhd->timer, 0);
 		}
 		vhd->count_live_pss++;
 		pss->next = vhd->live_pss_list;
 		vhd->live_pss_list = pss;
 		/* if we have scan results, update them.  Otherwise wait */
-		if (vhd->count_ap_records) {
+//		if (vhd->count_ap_records) {
 			pss->scan_state = SCAN_STATE_INITIAL;
 			lws_callback_on_writable(wsi);
-		}
+//		}
 		break;
 
 	case LWS_CALLBACK_SERVER_WRITEABLE:
@@ -481,6 +610,10 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 				      " \"unixtime\":\"%llu\",\n"
 				      " \"certissuer\":\"%s\",\n"
 				      " \"certsubject\":\"%s\",\n"
+				      " \"le_dns\":\"%s\",\n"
+				      " \"le_email\":\"%s\",\n"
+				      " \"acme_state\":\"%d\",\n"
+				      " \"acme_msg\":\"%s\",\n"
 				      " \"button\":\"%d\",\n"
 				      " \"group\":\"%s\",\n"
 				      " \"role\":\"%s\",\n",
@@ -499,6 +632,10 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 				      vhd->cert_remaining_days,
 				      (unsigned long long)t.tv_sec,
 				      ir.ns.name, subject,
+				      lws_esp32.le_dns,
+				      lws_esp32.le_email,
+				      vhd->acme_state,
+				      vhd->acme_msg,
 				      ((volatile struct lws_esp32 *)(&lws_esp32))->button_is_down,
 				      group, role);
 			p += snprintf((char *)p, end - p,
@@ -561,6 +698,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 					ssid, pp, use);
 			}
 			nvs_close(nvh);
+			pss->ap_record = 0;
 
 			p += snprintf((char *)p, end - p,
                                       "], \"aps\":[\n");
@@ -570,27 +708,36 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 			break;
 
 		case SCAN_STATE_LIST:
+			lwscan = vhd->known_aps_list;
+
+			n = pss->ap_record;
+			while (lwscan && n--)
+				lwscan = lwscan->next;
+
 			for (m = 0; m < 6; m++) {
 				n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
-				if (pss->ap_record >= vhd->count_ap_records)
+				if (!lwscan)
 					goto scan_state_final;
 
 				if (pss->subsequent)
 					*p++ = ',';
 				pss->subsequent = 1;
+				pss->ap_record++;
 
-				r = &vhd->ap_records[(int)pss->ap_record++];
 				p += snprintf((char *)p, end - p,
 					      "{\"ssid\":\"%s\",\n"
 					       "\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\",\n"
 					       "\"rssi\":\"%d\",\n"
 					       "\"chan\":\"%d\",\n"
 					       "\"auth\":\"%d\"}\n",
-						r->ssid,
-						r->bssid[0], r->bssid[1], r->bssid[2],
-						r->bssid[3], r->bssid[4], r->bssid[5],
-						r->rssi, r->primary, r->authmode);
-				if (pss->ap_record >= vhd->count_ap_records)
+					       lwscan->ssid,
+					       lwscan->bssid[0], lwscan->bssid[1], lwscan->bssid[2],
+					       lwscan->bssid[3], lwscan->bssid[4], lwscan->bssid[5],
+					       lws_wifi_scan_rssi(lwscan),
+					       lwscan->channel, lwscan->authmode);
+
+				lwscan = lwscan->next;
+				if (!lwscan)
 					pss->scan_state = SCAN_STATE_FINAL;
 			}
 			break;
@@ -600,6 +747,7 @@ scan_state_final:
 			n = LWS_WRITE_CONTINUATION;
 			p += sprintf((char *)p, "]\n}\n");
 			if (pss->changed_partway) {
+				pss->changed_partway = 0;
 				pss->subsequent = 0;
 				pss->scan_state = SCAN_STATE_INITIAL;
 			} else {
@@ -622,6 +770,17 @@ issue:
 
 		break;
 
+	case LWS_CALLBACK_VHOST_CERT_UPDATE:
+		lwsl_notice("LWS_CALLBACK_VHOST_CERT_UPDATE: %d\n", (int)len);
+		vhd->acme_state = (int)len;
+		if (in) {
+			strncpy(vhd->acme_msg, in, sizeof(vhd->acme_msg) - 1);
+			vhd->acme_msg[sizeof(vhd->acme_msg) - 1] = '\0';
+			lwsl_notice("acme_msg: %s\n", (char *)in);
+		}
+		lws_callback_on_writable_all_protocol_vhost(vhd->vhost, vhd->protocol);
+		break;
+
 	case LWS_CALLBACK_RECEIVE:
 		{
 			const char *sect = "\"app\": {", *b;
@@ -644,6 +803,10 @@ issue:
 			if (strstr((const char *)in, "\"reset\""))
 				goto sched_reset;
 
+			if (!strncmp((const char *)in, "{\"job\":\"start-le\"", 17))
+				goto start_le;
+
+
 			if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
 				lwsl_err("Unable to open nvs\n");
 				break;
@@ -662,16 +825,6 @@ issue:
 				if (n == 8 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
 					continue;
 
-				//lwsl_notice("%s: %s '%s'\n", __func__, store_json[n].nvs, p);
-				if (n == 9) {
-					strncpy(lws_esp32.group, p, sizeof(lws_esp32.group) - 1);
-					lws_esp32.group[sizeof(lws_esp32.group) - 1] = '\0';
-				}
-				if (n == 10) {
-					strncpy(lws_esp32.role, p, sizeof(lws_esp32.role) - 1);
-					lws_esp32.role[sizeof(lws_esp32.role) - 1] = '\0';
-				}
-
 				if (lws_nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) {
 					lwsl_err("Unable to store %s in nvm\n", store_json[n].nvs);
 					goto bail_nvs;
@@ -767,6 +920,75 @@ auton:
 				vhd->autonomous_update = 0;
 
 			break;
+
+start_le:
+			lws_esp32.acme = 1; /* hold off scanning */
+			puts(in);
+			/*
+			 * {"job":"start-le","cn":"home.warmcat.com",
+			 * "email":"andy@warmcat.com", "staging":"true"}
+			 */
+
+			if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
+				lwsl_err("Unable to open nvs\n");
+				break;
+			}
+
+			n = 0;
+			b = strstr(in, ",\"cn\":\"");
+			if (b) {
+				b += 7;
+				while (*b && *b != '\"' && n < sizeof(lws_esp32.le_dns) - 1)
+					lws_esp32.le_dns[n++] = *b++;
+			}
+			lws_esp32.le_dns[n] = '\0';
+
+			lws_nvs_set_str(nvh, "acme-cn", lws_esp32.le_dns);
+			n = 0;
+			b = strstr(in, ",\"email\":\"");
+			if (b) {
+				b += 10;
+				while (*b && *b != '\"' && n < sizeof(lws_esp32.le_email) - 1)
+					lws_esp32.le_email[n++] = *b++;
+			}
+			lws_esp32.le_email[n] = '\0';
+			lws_nvs_set_str(nvh, "acme-email", lws_esp32.le_email);
+			nvs_commit(nvh);
+
+			nvs_close(nvh);
+
+			n = 1;
+			b = strstr(in, ",\"staging\":\"");
+			if (b)
+				lwsl_notice("staging: %s\n", b);
+			if (b && b[12] == 'f')
+				n = 0;
+
+			lwsl_notice("cn: %s, email: %s, staging: %d\n", lws_esp32.le_dns, lws_esp32.le_email, n);
+
+			{
+				struct lws_acme_cert_aging_args caa;
+
+				memset(&caa, 0, sizeof(caa));
+				caa.vh = vhd->vhost;
+
+				caa.element_overrides[LWS_TLS_REQ_ELEMENT_COMMON_NAME] = lws_esp32.le_dns;
+				caa.element_overrides[LWS_TLS_REQ_ELEMENT_EMAIL] = lws_esp32.le_email;
+
+				if (n)
+					caa.element_overrides[LWS_TLS_SET_DIR_URL] =
+							"https://acme-staging.api.letsencrypt.org/directory"; /* staging */
+				else
+					caa.element_overrides[LWS_TLS_SET_DIR_URL] =
+						"https://acme-v01.api.letsencrypt.org/directory"; /* real */
+
+				lws_callback_vhost_protocols_vhost(vhd->vhost,
+						LWS_CALLBACK_VHOST_CERT_AGING,
+							(void *)&caa, 0);
+			}
+
+			break;
+
 		}
 
 	case LWS_CALLBACK_CLOSED:
@@ -802,7 +1024,7 @@ auton:
 			pss->filename[0] = '\0';
 			pss->file_length = 0;
 		}
-
+		//puts((const char *)in);
 		/* let it parse the POST data */
 		if (lws_spa_process(pss->spa, in, len))
 			return -1;
@@ -813,6 +1035,14 @@ auton:
 		/* call to inform no more payload data coming */
 		lws_spa_finalize(pss->spa);
 
+		for (n = 0; n < ARRAY_SIZE(param_names); n++)
+			if (lws_spa_get_string(pss->spa, n))
+				lwsl_notice(" Param %s: %s\n", param_names[n],
+					    lws_spa_get_string(pss->spa, n));
+			else
+				lwsl_notice(" Param %s: (none)\n",
+					    param_names[n]);
+
 		if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
 			lwsl_err("Unable to open nvs\n");
 			break;
@@ -838,15 +1068,29 @@ auton:
 				nvs_commit(nvh);
 			}
 		}
+
+		if (lws_spa_get_string(pss->spa, EPN_GROUP)) {
+			if (lws_nvs_set_str(nvh, "group", lws_spa_get_string(pss->spa, EPN_GROUP)) != ESP_OK) {
+				lwsl_err("Unable to store group in nvm\n");
+				goto bail_nvs;
+			}
+
+			nvs_commit(nvh);
+		}
+
+		if (lws_spa_get_string(pss->spa, EPN_ROLE)) {
+			if (lws_nvs_set_str(nvh, "role", lws_spa_get_string(pss->spa, EPN_ROLE)) != ESP_OK) {
+				lwsl_err("Unable to store group in nvm\n");
+				goto bail_nvs;
+			}
+
+			nvs_commit(nvh);
+		}
+
 		nvs_close(nvh);
 
-		pp = lws_spa_get_string(pss->spa, EPN_SERIAL);
-		if (!pp)
-			pp = "unknown";
 		pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1,
-				"<html>Rebooting after storing certs...<br>connect to AP '<b>config-%s-%s</b>' and continue here: "
-				"<a href=\"https://192.168.4.1\">https://192.168.4.1</a></html>",
-				lws_esp32.model, pp);
+				"<html>OK</html>");
 
 		if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
 			goto bail;
@@ -860,11 +1104,7 @@ auton:
 			goto bail;
 
 		n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
-		if (n < 0)
-			goto bail;
-
-		lws_callback_on_writable(wsi);
-		break;
+		goto bail;
 
 	case LWS_CALLBACK_HTTP_WRITEABLE:
 		lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n",