diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1319,6 +1319,23 @@ Controls generic server settings.
     Instruct HTTP clients not to send request headers longer than this
     many bytes. (default: 1024)
 
+``bundle1``
+    Whether to allow clients to push and pull using the legacy bundle1
+    exchange format. (default: True)
+
+``bundle1.push``
+    Whether to allow clients to push using the legacy bundle1 exchange
+    format. (default: True)
+
+``bundle1.pull``
+    Whether to allow clients to pull using the legacy bundle1 exchange
+    format. (default: True)
+
+    Large repositories using the *generaldelta* storage format should
+    consider setting this option because converting *generaldelta*
+    repositories to the exchange format required by the bundle1 data
+    format can consume a lot of CPU.
+
 ``smtp``
 --------
 
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -30,6 +30,10 @@ from . import (
     util,
 )
 
+bundle2required = _(
+    'incompatible Mercurial client; bundle2 required\n'
+    '(see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n')
+
 class abstractserverproto(object):
     """abstract class that summarizes the protocol API
 
@@ -487,6 +491,14 @@ def options(cmd, keys, others):
                          % (cmd, ",".join(others)))
     return opts
 
+def bundle1allowed(ui, action):
+    """Whether a bundle1 operation is allowed from the server."""
+    v = ui.configbool('server', 'bundle1.%s' % action, None)
+    if v is not None:
+        return v
+
+    return ui.configbool('server', 'bundle1', True)
+
 # list of commands
 commands = {}
 
@@ -652,6 +664,11 @@ def getbundle(repo, proto, others):
         elif keytype != 'plain':
             raise KeyError('unknown getbundle option type %s'
                            % keytype)
+
+    if not bundle1allowed(repo.ui, 'pull'):
+        if not exchange.bundle2requested(opts.get('bundlecaps')):
+            return ooberror(bundle2required)
+
     cg = exchange.getbundle(repo, 'serve', **opts)
     return streamres(proto.groupchunks(cg))
 
@@ -763,6 +780,10 @@ def unbundle(repo, proto, heads):
             proto.getfile(fp)
             fp.seek(0)
             gen = exchange.readbundle(repo.ui, fp, None)
+            if (isinstance(gen, changegroupmod.cg1unpacker)
+                and not bundle1allowed(repo.ui, 'push')):
+                return ooberror(bundle2required)
+
             r = exchange.unbundle(repo, gen, their_heads, 'serve',
                                   proto._client())
             if util.safehasattr(r, 'addpart'):
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -951,3 +951,86 @@ Test lazily acquiring the lock during un
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
+
+  $ cd ..
+
+Servers can disable bundle1 for clone/pull operations
+
+  $ killdaemons.py
+  $ hg init bundle2onlyserver
+  $ cd bundle2onlyserver
+  $ cat > .hg/hgrc << EOF
+  > [server]
+  > bundle1.pull = false
+  > EOF
+
+  $ touch foo
+  $ hg -q commit -A -m initial
+
+  $ hg serve -p $HGPORT -d --pid-file=hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT/ not-bundle2
+  requesting all changes
+  abort: remote error:
+  incompatible Mercurial client; bundle2 required
+  (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
+  [255]
+  $ killdaemons.py
+
+Verify the global server.bundle1 option works
+
+  $ cat > .hg/hgrc << EOF
+  > [server]
+  > bundle1 = false
+  > EOF
+  $ hg serve -p $HGPORT -d --pid-file=hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ hg --config experimental.bundle2-exp=false clone http://localhost:$HGPORT not-bundle2
+  requesting all changes
+  abort: remote error:
+  incompatible Mercurial client; bundle2 required
+  (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
+  [255]
+  $ killdaemons.py
+
+Verify bundle1 pushes can be disabled
+
+  $ cat > .hg/hgrc << EOF
+  > [server]
+  > bundle1.push = false
+  > [web]
+  > allow_push = *
+  > push_ssl = false
+  > EOF
+
+  $ hg serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ cd ..
+
+  $ hg clone http://localhost:$HGPORT bundle2-only
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd bundle2-only
+  $ echo commit > foo
+  $ hg commit -m commit
+  $ hg --config experimental.bundle2-exp=false push
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: remote error:
+  incompatible Mercurial client; bundle2 required
+  (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
+  [255]
+
+  $ hg push
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files