diff --git a/op-batcher/batcher/batch_submitter.go b/op-batcher/batcher/batch_submitter.go
index 34af6cc46893ac9e9c2d6519ea6cb89115bc843b..2489188b23c718b7e3316f8a89e30d1424d44e2c 100644
--- a/op-batcher/batcher/batch_submitter.go
+++ b/op-batcher/batcher/batch_submitter.go
@@ -4,9 +4,6 @@ import (
 	"context"
 	"fmt"
 	_ "net/http/pprof"
-	"os"
-	"os/signal"
-	"syscall"
 
 	gethrpc "github.com/ethereum/go-ethereum/rpc"
 	"github.com/urfave/cli"
@@ -16,6 +13,7 @@ import (
 	"github.com/ethereum-optimism/optimism/op-batcher/rpc"
 	opservice "github.com/ethereum-optimism/optimism/op-service"
 	oplog "github.com/ethereum-optimism/optimism/op-service/log"
+	"github.com/ethereum-optimism/optimism/op-service/opio"
 	oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
 	oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
 )
@@ -98,14 +96,7 @@ func Main(version string, cliCtx *cli.Context) error {
 	m.RecordInfo(version)
 	m.RecordUp()
 
-	interruptChannel := make(chan os.Signal, 1)
-	signal.Notify(interruptChannel, []os.Signal{
-		os.Interrupt,
-		os.Kill,
-		syscall.SIGTERM,
-		syscall.SIGQUIT,
-	}...)
-	<-interruptChannel
+	opio.BlockOnInterrupts()
 	if err := server.Stop(); err != nil {
 		l.Error("Error shutting down http server: %w", err)
 	}
diff --git a/op-challenger/challenger/entrypoint.go b/op-challenger/challenger/entrypoint.go
index be10c47a2c4f5213aa98a52bdd8d859cc8a4576b..6d2bd3aac42a1d0afc29a93ec5a8d4e261d9d625 100644
--- a/op-challenger/challenger/entrypoint.go
+++ b/op-challenger/challenger/entrypoint.go
@@ -4,14 +4,12 @@ import (
 	"context"
 	"fmt"
 	_ "net/http/pprof"
-	"os"
-	"os/signal"
-	"syscall"
 
 	"github.com/ethereum/go-ethereum/log"
 
 	"github.com/ethereum-optimism/optimism/op-challenger/config"
 	"github.com/ethereum-optimism/optimism/op-challenger/metrics"
+	"github.com/ethereum-optimism/optimism/op-service/opio"
 	oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
 	oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
 )
@@ -73,14 +71,7 @@ func Main(logger log.Logger, version string, cfg *config.Config) error {
 	m.RecordInfo(version)
 	m.RecordUp()
 
-	interruptChannel := make(chan os.Signal, 1)
-	signal.Notify(interruptChannel, []os.Signal{
-		os.Interrupt,
-		os.Kill,
-		syscall.SIGTERM,
-		syscall.SIGQUIT,
-	}...)
-	<-interruptChannel
+	opio.BlockOnInterrupts()
 	cancel()
 
 	return nil
diff --git a/op-node/cmd/main.go b/op-node/cmd/main.go
index 09db5f4cacaccee5c26dfa576e38b4307e6f335d..e8f682968628ee40f602dd5b1aa3ca3fa9fee560 100644
--- a/op-node/cmd/main.go
+++ b/op-node/cmd/main.go
@@ -4,9 +4,7 @@ import (
 	"context"
 	"net"
 	"os"
-	"os/signal"
 	"strconv"
-	"syscall"
 
 	"github.com/ethereum-optimism/optimism/op-node/chaincfg"
 	"github.com/ethereum-optimism/optimism/op-node/cmd/doc"
@@ -25,6 +23,7 @@ import (
 	"github.com/ethereum-optimism/optimism/op-node/version"
 	opservice "github.com/ethereum-optimism/optimism/op-service"
 	oplog "github.com/ethereum-optimism/optimism/op-service/log"
+	"github.com/ethereum-optimism/optimism/op-service/opio"
 	oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
 )
 
@@ -162,14 +161,7 @@ func RollupNodeMain(ctx *cli.Context) error {
 		defer pprofCancel()
 	}
 
-	interruptChannel := make(chan os.Signal, 1)
-	signal.Notify(interruptChannel, []os.Signal{
-		os.Interrupt,
-		os.Kill,
-		syscall.SIGTERM,
-		syscall.SIGQUIT,
-	}...)
-	<-interruptChannel
+	opio.BlockOnInterrupts()
 
 	return nil
 
diff --git a/op-proposer/proposer/l2_output_submitter.go b/op-proposer/proposer/l2_output_submitter.go
index 92be596297154eb8e98c5f8d73a7875a23e1495c..fd7335bf9df5780b620133ca7fb5f5f0eaa494bd 100644
--- a/op-proposer/proposer/l2_output_submitter.go
+++ b/op-proposer/proposer/l2_output_submitter.go
@@ -6,10 +6,7 @@ import (
 	"fmt"
 	"math/big"
 	_ "net/http/pprof"
-	"os"
-	"os/signal"
 	"sync"
-	"syscall"
 	"time"
 
 	"github.com/ethereum/go-ethereum/accounts/abi"
@@ -27,6 +24,7 @@ import (
 	opservice "github.com/ethereum-optimism/optimism/op-service"
 	opclient "github.com/ethereum-optimism/optimism/op-service/client"
 	oplog "github.com/ethereum-optimism/optimism/op-service/log"
+	"github.com/ethereum-optimism/optimism/op-service/opio"
 	oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
 	oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
 	"github.com/ethereum-optimism/optimism/op-service/txmgr"
@@ -103,14 +101,7 @@ func Main(version string, cliCtx *cli.Context) error {
 	m.RecordInfo(version)
 	m.RecordUp()
 
-	interruptChannel := make(chan os.Signal, 1)
-	signal.Notify(interruptChannel, []os.Signal{
-		os.Interrupt,
-		os.Kill,
-		syscall.SIGTERM,
-		syscall.SIGQUIT,
-	}...)
-	<-interruptChannel
+	opio.BlockOnInterrupts()
 	cancel()
 
 	return nil
diff --git a/op-service/opio/interrupts.go b/op-service/opio/interrupts.go
new file mode 100644
index 0000000000000000000000000000000000000000..191b97e1ecb05653b35a91c0e471a5d3b6f7fa5f
--- /dev/null
+++ b/op-service/opio/interrupts.go
@@ -0,0 +1,26 @@
+package opio
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+// DefaultInterruptSignals is a set of default interrupt signals.
+var DefaultInterruptSignals = []os.Signal{
+	os.Interrupt,
+	os.Kill,
+	syscall.SIGTERM,
+	syscall.SIGQUIT,
+}
+
+// BlockOnInterrupts blocks until a SIGTERM is received.
+// Passing in signals will override the default signals.
+func BlockOnInterrupts(signals ...os.Signal) {
+	if len(signals) == 0 {
+		signals = DefaultInterruptSignals
+	}
+	interruptChannel := make(chan os.Signal, 1)
+	signal.Notify(interruptChannel, signals...)
+	<-interruptChannel
+}