diff --git a/README.md b/README.md
index 7148fe4..23d6b23 100644
--- a/README.md
+++ b/README.md
@@ -83,10 +83,24 @@ Note that if you are using a self-signed or otherwise invalid certificate, the
 browser will not allow the connection by default.  Additional browser configs
 or command line options may be necessary to use an invalid TLS certificate.
 
+## Controlling which network interfaces are listened to
+
+By default, jasmine-browser-runner listens to all available network interfaces.
+You might need that if you're using a remote grid such as Saucelabs. If you 
+don't need that, you can improve security by listening only to localhost. 
+
+```javascript
+export default {
+  // ...
+  "listenAddress": "localhost",
+  // ...
+}
+```
+
 ## Hostname support
 
-To serve tests on a specific interface or IP, you can specify a hostname in
-`jasmine-browser.mjs`:
+If you need to access your tests via a specific hostname, you can do that by
+setting the `hostname` configuration property:
 
 ```javascript
 export default {
@@ -98,6 +112,11 @@ export default {
 
 This can also be specified on the command line with `--hostname`.
 
+Setting `hostname` but not `listenAddress` has the same effect as setting
+`listenAddress` to the same value as `hostname`. If you need to set a hostname
+but retain the default behavior of listening to all network interfaces, you can
+do that by setting `listenAddress` to `"*"`.
+
 There are a few important caveats when doing this:
 
 1. This name must either be an IP or a name that can really be resolved on your
diff --git a/lib/server.js b/lib/server.js
index 233cfec..3544b25 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -224,14 +224,20 @@ class Server {
     const tlsCert = serverOptions.tlsCert || this.options.tlsCert;
     const tlsKey = serverOptions.tlsKey || this.options.tlsKey;
     const hostname = serverOptions.hostname || this.options.hostname;
+    // The last two fallbacks here are necessary for backwards compatibility.
+    let listenAddress =
+      serverOptions.listenAddress ||
+      this.options.listenAddress ||
+      hostname ||
+      '';
+
+    if (listenAddress === '*') {
+      listenAddress = '';
+    }
 
-    // NOTE: Before hostname support, jasmine-browser-runner would listen on
-    // all IPs (no hostname) and point browsers to "localhost".  We preserve
-    // backward compatibility here by using different defaults for these two
-    // things.
     const listenOptions = {
       port,
-      host: hostname || '',
+      host: listenAddress,
     };
     this._httpHostname = hostname || 'localhost';
 
diff --git a/lib/types.js b/lib/types.js
index 734ab7f..2c9d3b0 100644
--- a/lib/types.js
+++ b/lib/types.js
@@ -52,10 +52,20 @@
  * @type string
  */
 /**
- * The hostname to use.  This influences both the URL given to browsers and the
- * addresses on which the socket listens.  If blank, for backward
- * compatibility, the browsers will be pointed to localhost, but the listening
- * socket will listen on all IPs.
+ * The hostname or IP address of the network interface to listen on. For
+ * security, this should be set to localhost or an equivalent unless you need
+ * the server to be accessible over other network interfaces. Set to "*" to
+ * listen on all interfaces, which may be required by some remote Selenium
+ * grids.
+ * @name ServerCtorOptions#listenAddress
+ * @default The value of {@link ServerCtorOptions#hostname} or {@link ServerStartOptions#hostname} if configured, otherwise "*"
+ * @type string | undefined
+ */
+/**
+ * The hostname to use in the URL given to browsers. For backward compatibility,
+ * setting this property without also setting {@link ServerCtorOptions#listenAddress}
+ * or {@link ServerStartOptions#listenAddress} has the same effect as setting
+ * the listen address to the hostname.
  * @name ServerCtorOptions#hostname
  * @default "localhost"
  * @type string | undefined
@@ -307,7 +317,10 @@
 /**
  * @see ServerCtorOptions#hostname
  * @name ServerStartOptions#hostname
- * @type string
+ */
+/**
+ * @see ServerCtorOptions#listenAddress
+ * @name ServerStartOptions#listenAddress
  */
 
 /**
diff --git a/spec/serverSpec.js b/spec/serverSpec.js
index 038e036..7d7ae0f 100644
--- a/spec/serverSpec.js
+++ b/spec/serverSpec.js
@@ -482,10 +482,9 @@ describe('server', function() {
       }
 
       describe('Passing options to the ctor', function() {
-        describe('When hostname is not specified', function() {
+        describe('When neither hostname nor listenAddress is specified', function() {
           it('listens to all interfaces', async function() {
             const options = baseCtorOptions();
-            expect(options.hostname).toBeFalsy();
             const http = makeMockNodeServer('http');
             this.server = new Server(options, { http });
 
@@ -498,7 +497,7 @@ describe('server', function() {
           });
         });
 
-        describe('When hostname is specified', function() {
+        describe('When hostname but not listenAddress is specified', function() {
           it('listens to the specified hostname', async function() {
             const options = baseCtorOptions();
             options.hostname = 'specific.example.com';
@@ -513,10 +512,59 @@ describe('server', function() {
             );
           });
         });
+
+        describe('When listenAddress but not hostname is specified', function() {
+          it('listens to the specified listenAddress', async function() {
+            const options = baseCtorOptions();
+            options.listenAddress = 'specific.example.com';
+            const http = makeMockNodeServer('http');
+            this.server = new Server(options, { http });
+
+            await this.server.start();
+
+            expect(http.listen).toHaveBeenCalledWith(
+              jasmine.objectContaining({ host: 'specific.example.com' }),
+              jasmine.any(Function)
+            );
+          });
+        });
+
+        describe('When both hostname and listenAddress are specified', function() {
+          it('listens to the specified listenAddress', async function() {
+            const options = baseCtorOptions();
+            options.listenAddress = 'specific.example.com';
+            options.hostname = 'other.example.com';
+            const http = makeMockNodeServer('http');
+            this.server = new Server(options, { http });
+
+            await this.server.start();
+
+            expect(http.listen).toHaveBeenCalledWith(
+              jasmine.objectContaining({ host: 'specific.example.com' }),
+              jasmine.any(Function)
+            );
+          });
+        });
+
+        describe('When listenAddress is "*"', function() {
+          it('listens to all interfaces', async function() {
+            const options = baseCtorOptions();
+            options.listenAddress = '*';
+            const http = makeMockNodeServer('http');
+            this.server = new Server(options, { http });
+
+            await this.server.start();
+
+            expect(http.listen).toHaveBeenCalledWith(
+              jasmine.objectContaining({ host: '' }),
+              jasmine.any(Function)
+            );
+          });
+        });
       });
 
       describe('Passing options to start', function() {
-        describe('When hostname is not specified', function() {
+        describe('When neither hostname nor listenAddress is specified', function() {
           it('listens to all interfaces', async function() {
             const http = makeMockNodeServer('http');
             this.server = new Server(baseCtorOptions(), { http });
@@ -530,7 +578,7 @@ describe('server', function() {
           });
         });
 
-        describe('When hostname is specified', function() {
+        describe('When hostname but not listenAddress is specified', function() {
           it('listens to the specified hostname', async function() {
             const http = makeMockNodeServer('http');
             this.server = new Server(baseCtorOptions(), { http });
@@ -543,6 +591,53 @@ describe('server', function() {
             );
           });
         });
+
+        describe('When listenAddress but not hostname is specified', function() {
+          it('listens to the specified listenAddress', async function() {
+            const http = makeMockNodeServer('http');
+            this.server = new Server(baseCtorOptions(), { http });
+
+            await this.server.start({
+              listenAddress: 'specific.example.com',
+            });
+
+            expect(http.listen).toHaveBeenCalledWith(
+              jasmine.objectContaining({ host: 'specific.example.com' }),
+              jasmine.any(Function)
+            );
+          });
+        });
+
+        describe('When both hostname and listenAddress are specified', function() {
+          it('listens to the specified listenAddress', async function() {
+            const http = makeMockNodeServer('http');
+            this.server = new Server(baseCtorOptions(), { http });
+
+            await this.server.start({
+              listenAddress: 'specific.example.com',
+              hostname: 'other.example.com',
+            });
+
+            expect(http.listen).toHaveBeenCalledWith(
+              jasmine.objectContaining({ host: 'specific.example.com' }),
+              jasmine.any(Function)
+            );
+          });
+        });
+
+        describe('When listenAddress is "*"', function() {
+          it('listens to all interfaces', async function() {
+            const http = makeMockNodeServer('http');
+            this.server = new Server(baseCtorOptions(), { http });
+
+            await this.server.start({ listenAddress: '*' });
+
+            expect(http.listen).toHaveBeenCalledWith(
+              jasmine.objectContaining({ host: '' }),
+              jasmine.any(Function)
+            );
+          });
+        });
       });
     });