diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go
index aa5b2d8a023912f7e67dacbebc335463ba45aa23..d284e7b00b61270618c539932e7223c41572dad7 100644
--- a/consensus/ethash/ethash.go
+++ b/consensus/ethash/ethash.go
@@ -555,11 +555,18 @@ func (ethash *Ethash) Threads() int {
 
 // SetThreads updates the number of mining threads currently enabled. Calling
 // this method does not start mining, only sets the thread count. If zero is
-// specified, the miner will use all cores of the machine.
+// specified, the miner will use all cores of the machine. Setting a thread
+// count below zero is allowed and will cause the miner to idle, without any
+// work being done.
 func (ethash *Ethash) SetThreads(threads int) {
 	ethash.lock.Lock()
 	defer ethash.lock.Unlock()
 
+	// If we're running a shared PoW, set the thread count on that instead
+	if ethash.shared != nil {
+		ethash.shared.SetThreads(threads)
+		return
+	}
 	// Update the threads and ping any running seal to pull in any changes
 	ethash.threads = threads
 	select {
diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go
index 9a000ed316a066d5a1ab19b418583900da748fee..784e8f6491f267786e6e5e37b159bfca010f1f39 100644
--- a/consensus/ethash/sealer.go
+++ b/consensus/ethash/sealer.go
@@ -61,6 +61,9 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop
 	if threads == 0 {
 		threads = runtime.NumCPU()
 	}
+	if threads < 0 {
+		threads = 0 // Allows disabling local mining without extra logic around local/remote
+	}
 	var pend sync.WaitGroup
 	for i := 0; i < threads; i++ {
 		pend.Add(1)
diff --git a/eth/api.go b/eth/api.go
index 041ccd397ef09135d421d6fcaf58c861d1832e18..9470705159a0c9bbefc91640519051bb14344459 100644
--- a/eth/api.go
+++ b/eth/api.go
@@ -139,16 +139,17 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
 // threads allowed to use.
 func (api *PrivateMinerAPI) Start(threads *int) error {
 	// Set the number of threads if the seal engine supports it
-	if threads != nil {
-		type threaded interface {
-			SetThreads(threads int)
-		}
-		if th, ok := api.e.engine.(threaded); ok {
-			log.Info("Updated mining threads", "threads", *threads)
-			th.SetThreads(*threads)
-		} else {
-			log.Warn("Current seal engine isn't threaded")
-		}
+	if threads == nil {
+		threads = new(int)
+	} else if *threads == 0 {
+		*threads = -1 // Disable the miner from within
+	}
+	type threaded interface {
+		SetThreads(threads int)
+	}
+	if th, ok := api.e.engine.(threaded); ok {
+		log.Info("Updated mining threads", "threads", *threads)
+		th.SetThreads(*threads)
 	}
 	// Start the miner and return
 	if !api.e.IsMining() {
@@ -159,6 +160,12 @@ func (api *PrivateMinerAPI) Start(threads *int) error {
 
 // Stop the miner
 func (api *PrivateMinerAPI) Stop() bool {
+	type threaded interface {
+		SetThreads(threads int)
+	}
+	if th, ok := api.e.engine.(threaded); ok {
+		th.SetThreads(-1)
+	}
 	api.e.StopMining()
 	return true
 }
diff --git a/eth/backend.go b/eth/backend.go
index f241d5f34fecebed36c21d25e2c2c58f6983c0f7..df5460201fba876f8a5a0515a7a53150eb520782 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -240,8 +240,10 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *Config, chainConfig
 		log.Warn("Ethash used in shared mode")
 		return ethash.NewShared()
 	default:
-		return ethash.New(ctx.ResolvePath(config.EthashCacheDir), config.EthashCachesInMem, config.EthashCachesOnDisk,
+		engine := ethash.New(ctx.ResolvePath(config.EthashCacheDir), config.EthashCachesInMem, config.EthashCachesOnDisk,
 			config.EthashDatasetDir, config.EthashDatasetsInMem, config.EthashDatasetsOnDisk)
+		engine.SetThreads(-1) // Disable CPU mining
+		return engine
 	}
 }