diff --git a/bin/options.js b/bin/options.js
index 35db9b3158..3670ffe15d 100644
--- a/bin/options.js
+++ b/bin/options.js
@@ -87,6 +87,11 @@ const options = {
     group: SSL_GROUP,
     describe: 'HTTPS',
   },
+  http2: {
+    type: 'boolean',
+    group: SSL_GROUP,
+    describe: 'HTTP/2, must be used with HTTPS',
+  },
   key: {
     type: 'string',
     describe: 'Path to a SSL key.',
diff --git a/lib/Server.js b/lib/Server.js
index 60e8de82a9..7134d10b87 100644
--- a/lib/Server.js
+++ b/lib/Server.js
@@ -89,13 +89,19 @@ class Server {
     if (options.lazy && !options.filename) {
       throw new Error("'filename' option must be set in lazy mode.");
     }
-
+    
+    // if the user enables http2, we can safely enable https
+    if (options.http2 && !options.https) {
+      options.https = true;
+    }
+    
     updateCompiler(compiler, options);
 
     this.stats =
       options.stats && Object.keys(options.stats).length
         ? options.stats
         : Server.DEFAULT_STATS;
+
     this.hot = options.hot || options.hotOnly;
     this.headers = options.headers;
     this.progress = options.progress;
@@ -647,7 +653,21 @@ class Server {
       options.https.key = options.https.key || fakeCert;
       options.https.cert = options.https.cert || fakeCert;
 
-      if (!options.https.spdy) {
+      // Only prevent HTTP/2 if http2 is explicitly set to false
+      const isHttp2 = options.http2 !== false;
+
+      // note that options.spdy never existed. The user was able
+      // to set options.https.spdy before, though it was not in the
+      // docs. Keep options.https.spdy if the user sets it for
+      // backwards compatability, but log a deprecation warning.
+      if (options.https.spdy) {
+        // for backwards compatability: if options.https.spdy was passed in before,
+        // it was not altered in any way
+        this.log.warn(
+          'Providing custom spdy server options is deprecated and will be removed in the next major version.'
+        );
+      } else {
+        // if the normal https server gets this option, it will not affect it.
         options.https.spdy = {
           protocols: ['h2', 'http/1.1'],
         };
@@ -662,7 +682,14 @@ class Server {
       // - https://github.com/nodejs/node/issues/21665
       // - https://github.com/webpack/webpack-dev-server/issues/1449
       // - https://github.com/expressjs/express/issues/3388
-      if (semver.gte(process.version, '10.0.0')) {
+      if (semver.gte(process.version, '10.0.0') || !isHttp2) {
+        if (options.http2) {
+          // the user explicitly requested http2 but is not getting it because
+          // of the node version.
+          this.log.warn(
+            'HTTP/2 is currently unsupported for Node 10.0.0 and above, but will be supported once Express supports it'
+          );
+        }
         this.listeningApp = https.createServer(options.https, app);
       } else {
         /* eslint-disable global-require */
diff --git a/lib/options.json b/lib/options.json
index 7e4e2b3db9..d3a3bdda62 100644
--- a/lib/options.json
+++ b/lib/options.json
@@ -162,6 +162,9 @@
         }
       ]
     },
+    "http2": {
+      "type": "boolean"
+    },
     "contentBase": {
       "anyOf": [
         {
@@ -321,6 +324,7 @@
       "disableHostCheck": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-disablehostcheck)",
       "public": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserver-public)",
       "https": "should be {Object|Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-https)",
+      "http2": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-http2)",
       "contentBase": "should be {Array} (https://webpack.js.org/configuration/dev-server/#devserver-contentbase)",
       "watchContentBase": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-watchcontentbase)",
       "open": "should be {String|Boolean} (https://webpack.js.org/configuration/dev-server/#devserver-open)",
diff --git a/lib/utils/createConfig.js b/lib/utils/createConfig.js
index 8804571a1e..16ff397637 100644
--- a/lib/utils/createConfig.js
+++ b/lib/utils/createConfig.js
@@ -136,6 +136,10 @@ function createConfig(config, argv, { port }) {
     options.https = true;
   }
 
+  if (argv.http2) {
+    options.http2 = true;
+  }
+
   if (argv.key) {
     options.key = argv.key;
   }
diff --git a/test/CreateConfig.test.js b/test/CreateConfig.test.js
index f8bbb12209..c1227fc3f9 100644
--- a/test/CreateConfig.test.js
+++ b/test/CreateConfig.test.js
@@ -560,6 +560,28 @@ describe('createConfig', () => {
     expect(config).toMatchSnapshot();
   });
 
+  it('http2 option', () => {
+    const config = createConfig(
+      webpackConfig,
+      Object.assign({}, argv, { https: true, http2: true }),
+      { port: 8080 }
+    );
+
+    expect(config).toMatchSnapshot();
+  });
+
+  it('http2 option (in devServer config)', () => {
+    const config = createConfig(
+      Object.assign({}, webpackConfig, {
+        devServer: { https: true, http2: true },
+      }),
+      argv,
+      { port: 8080 }
+    );
+
+    expect(config).toMatchSnapshot();
+  });
+
   it('key option', () => {
     const config = createConfig(
       webpackConfig,
diff --git a/test/Http2.test.js b/test/Http2.test.js
new file mode 100644
index 0000000000..d71e352043
--- /dev/null
+++ b/test/Http2.test.js
@@ -0,0 +1,120 @@
+'use strict';
+
+const path = require('path');
+const request = require('supertest');
+const semver = require('semver');
+const helper = require('./helper');
+const config = require('./fixtures/contentbase-config/webpack.config');
+
+const contentBasePublic = path.join(
+  __dirname,
+  'fixtures/contentbase-config/public'
+);
+
+describe('http2', () => {
+  let server;
+  let req;
+
+  // HTTP/2 will only work with node versions below 10.0.0
+  // since spdy is broken past that point, and this test will only
+  // work above Node 8.8.0, since it is the first version where the
+  // built-in http2 module is exposed without need for a flag
+  // (https://nodejs.org/en/blog/release/v8.8.0/)
+  // if someone is testing below this Node version and breaks this,
+  // their tests will not catch it, but CI will catch it.
+  if (
+    semver.gte(process.version, '8.8.0') &&
+    semver.lt(process.version, '10.0.0')
+  ) {
+    /* eslint-disable global-require */
+    const http2 = require('http2');
+    /* eslint-enable global-require */
+    describe('http2 works with https', () => {
+      beforeAll((done) => {
+        server = helper.start(
+          config,
+          {
+            contentBase: contentBasePublic,
+            https: true,
+            http2: true,
+          },
+          done
+        );
+        req = request(server.app);
+      });
+
+      it('confirm http2 client can connect', (done) => {
+        const client = http2.connect('https://localhost:8080', {
+          rejectUnauthorized: false,
+        });
+        client.on('error', (err) => console.error(err));
+
+        const http2Req = client.request({ ':path': '/' });
+
+        http2Req.on('response', (headers) => {
+          expect(headers[':status']).toEqual(200);
+        });
+
+        http2Req.setEncoding('utf8');
+        let data = '';
+        http2Req.on('data', (chunk) => {
+          data += chunk;
+        });
+        http2Req.on('end', () => {
+          expect(data).toEqual(expect.stringMatching(/Heyo/));
+          done();
+        });
+        http2Req.end();
+      });
+
+      afterAll(helper.close);
+    });
+  }
+
+  describe('server works with http2 option, but without https option', () => {
+    beforeAll((done) => {
+      server = helper.start(
+        config,
+        {
+          contentBase: contentBasePublic,
+          http2: true,
+        },
+        done
+      );
+      req = request(server.app);
+    });
+
+    it('Request to index', (done) => {
+      req.get('/').expect(200, /Heyo/, done);
+    });
+
+    afterAll(helper.close);
+  });
+
+  describe('https without http2 disables HTTP/2', () => {
+    beforeAll((done) => {
+      server = helper.start(
+        config,
+        {
+          contentBase: contentBasePublic,
+          https: true,
+          http2: false,
+        },
+        done
+      );
+      req = request(server.app);
+    });
+
+    it('Request to index', (done) => {
+      req
+        .get('/')
+        .expect(200, /Heyo/)
+        .then(({ res }) => {
+          expect(res.httpVersion).not.toEqual('2.0');
+          done();
+        });
+    });
+
+    afterAll(helper.close);
+  });
+});
diff --git a/test/__snapshots__/CreateConfig.test.js.snap b/test/__snapshots__/CreateConfig.test.js.snap
index 2c631ecd42..2f146b276b 100644
--- a/test/__snapshots__/CreateConfig.test.js.snap
+++ b/test/__snapshots__/CreateConfig.test.js.snap
@@ -474,6 +474,38 @@ Object {
 }
 `;
 
+exports[`createConfig http2 option (in devServer config) 1`] = `
+Object {
+  "hot": true,
+  "hotOnly": false,
+  "http2": true,
+  "https": true,
+  "noInfo": true,
+  "port": 8080,
+  "publicPath": "/",
+  "stats": Object {
+    "cached": false,
+    "cachedAssets": false,
+  },
+}
+`;
+
+exports[`createConfig http2 option 1`] = `
+Object {
+  "hot": true,
+  "hotOnly": false,
+  "http2": true,
+  "https": true,
+  "noInfo": true,
+  "port": 8080,
+  "publicPath": "/",
+  "stats": Object {
+    "cached": false,
+    "cachedAssets": false,
+  },
+}
+`;
+
 exports[`createConfig https option (in devServer config) 1`] = `
 Object {
   "hot": true,