diff --git a/README.coding.md b/README.coding.md
index 33a13f6e3a85d22de880636fde1eb0832ad0d8d0..9cd9fdd085f3f472967c522c3fce61f77d60de2c 100644
--- a/README.coding.md
+++ b/README.coding.md
@@ -862,6 +862,40 @@ This allocation is only deleted / replaced when the connection accesses a
 URL region with a different protocol (or the default protocols[0] if no
 CALLBACK area matches it).
 
+@section BINDTODEV SO_BIND_TO_DEVICE
+
+The .bind_iface flag in the context / vhost creation struct lets you
+declare that you want all traffic for listen and transport on that
+vhost to be strictly bound to the network interface named in .iface.
+
+This Linux-only feature requires SO_BIND_TO_DEVICE, which in turn
+requires CAP_NET_RAW capability... root has this capability.
+
+However this feature needs to apply the binding also to accepted
+sockets during normal operation, which implies the server must run
+the whole time as root.
+
+You can avoid this by using the Linux capabilities feature to have
+the unprivileged user inherit just the CAP_NET_RAW capability.
+
+You can confirm this with the test server
+
+
+```
+ $ sudo /usr/local/bin/libwebsockets-test-server -u agreen -i eno1 -k
+```
+
+The part that ensures the capability is inherited by the unprivileged
+user is
+
+```
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+                        info.caps[0] = CAP_NET_RAW;
+                        info.count_caps = 1;
+#endif
+```
+
+
 @section dim Dimming webpage when connection lost
 
 The lws test plugins' html provides useful feedback on the webpage about if it
diff --git a/lib/context.c b/lib/context.c
index 248e29b303151a117b6832c689b6f557b9cc310e..9bf54657ef06135f2d69b5ce7c02cd426bedcefb 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -355,6 +355,10 @@ lws_create_vhost(struct lws_context *context,
 		vh->name = info->vhost_name;
 
 	vh->iface = info->iface;
+#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32)
+	vh->bind_iface = info->bind_iface;
+#endif
+
 	for (vh->count_protocols = 0;
 	     info->protocols[vh->count_protocols].callback;
 	     vh->count_protocols++)
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index b7385547da4bbe88046b1845b9fe9e69da4bcd3b..427fdae9be3efda6ed047f826e1c55d8af14f77c 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -1991,6 +1991,17 @@ struct lws_context_creation_info {
 	/**< CONTEXT: count of Linux capabilities in .caps[].  0 means
 	 * no capabilities will be inherited from root (the default) */
 #endif
+	int bind_iface;
+	/**< VHOST: nonzero to strictly bind sockets to the interface name in
+	 * .iface (eg, "eth2"), using SO_BIND_TO_DEVICE.
+	 *
+	 * Requires SO_BINDTODEVICE support from your OS and CAP_NET_RAW
+	 * capability.
+	 *
+	 * Notice that common things like access network interface IP from
+	 * your local machine use your lo / loopback interface and will be
+	 * disallowed by this.
+	 */
 
 	/* Add new things just above here ---^
 	 * This is part of the ABI, don't needlessly break compatibility
diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c
index ae73dfc49a2ae330a5f992c368236014fc78ff15..a2284f44251496131ac158bcdc766edf5f485512 100644
--- a/lib/lws-plat-unix.c
+++ b/lib/lws-plat-unix.c
@@ -255,6 +255,17 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, int fd)
 #endif
 	}
 
+#if defined(SO_BINDTODEVICE)
+	if (vhost->bind_iface) {
+		lwsl_info("binding listen skt to %s using SO_BINDTODEVICE\n", vhost->iface);
+		if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, vhost->iface,
+				strlen(vhost->iface)) < 0) {
+			lwsl_warn("Failed to bind to device %s\n", vhost->iface);
+			return 1;
+		}
+	}
+#endif
+
 	/* Disable Nagle */
 	optval = 1;
 #if defined (__sun)
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 48940bd674792c987ef9235b5ff17ca9dd25a629..993f6e14b210ba17b45d13e7247d0216070d0b0b 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -854,6 +854,9 @@ struct lws_vhost {
 	struct lws *lserv_wsi;
 	const char *name;
 	const char *iface;
+#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32)
+	int bind_iface;
+#endif
 	const struct lws_protocols *protocols;
 	void **protocol_vh_privs;
 	const struct lws_protocol_vhost_options *pvo;
diff --git a/test-server/test-server.c b/test-server/test-server.c
index 74c37da28776e2aeb76a2f80cb0d87375dba616d..d6ddb9ded18428e6398ac45902c535b0b568da6f 100644
--- a/test-server/test-server.c
+++ b/test-server/test-server.c
@@ -222,7 +222,7 @@ int main(int argc, char **argv)
 	info.port = 7681;
 
 	while (n >= 0) {
-		n = getopt_long(argc, argv, "eci:hsap:d:Dr:C:K:A:R:vu:g:P:", options, NULL);
+		n = getopt_long(argc, argv, "eci:hsap:d:Dr:C:K:A:R:vu:g:P:k", options, NULL);
 		if (n < 0)
 			continue;
 		switch (n) {
@@ -260,6 +260,13 @@ int main(int argc, char **argv)
 			interface_name[(sizeof interface_name) - 1] = '\0';
 			iface = interface_name;
 			break;
+		case 'k':
+			info.bind_iface = 1;
+#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
+			info.caps[0] = CAP_NET_RAW;
+			info.count_caps = 1;
+#endif
+			break;
 		case 'c':
 			close_testing = 1;
 			fprintf(stderr, " Close testing mode -- closes on "