diff --git a/app.go b/app.go
index 5d7fe4ac3..da3ca2307 100644
--- a/app.go
+++ b/app.go
@@ -802,7 +802,7 @@ func (app *App) Done() <-chan os.Signal {
 		return c
 	}
 
-	signal.Notify(c, _sigINT, _sigTERM)
+	signal.Notify(c, os.Interrupt, _sigINT, _sigTERM)
 	app.dones = append(app.dones, c)
 	return c
 }
diff --git a/app_test.go b/app_test.go
index 70f5a3730..53f814d37 100644
--- a/app_test.go
+++ b/app_test.go
@@ -1683,7 +1683,27 @@ func TestOptionString(t *testing.T) {
 }
 
 func TestMain(m *testing.M) {
-	goleak.VerifyTestMain(m)
+	if os.Getenv("VerifySignalHandler") != "" {
+		app := New(
+			NopLogger,
+			Invoke(func(lifecycle Lifecycle) {
+				lifecycle.Append(
+					Hook{
+						OnStart: func(ctx context.Context) error {
+							fmt.Fprintf(os.Stderr, "ready\n")
+							return nil
+						},
+						OnStop: func(ctx context.Context) error {
+							fmt.Fprintf(os.Stdout, "ONSTOP\n")
+							return nil
+						},
+					})
+			}),
+		)
+		app.Run()
+	} else {
+		goleak.VerifyTestMain(m)
+	}
 }
 
 type testLogger struct{ t *testing.T }
diff --git a/app_windows_test.go b/app_windows_test.go
new file mode 100644
index 000000000..c9d40b49d
--- /dev/null
+++ b/app_windows_test.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2020-2021 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+//go:build windows
+// +build windows
+
+package fx_test
+
+import (
+	"bytes"
+	"os"
+	"os/exec"
+	"syscall"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"golang.org/x/sys/windows"
+)
+
+func TestCtrlCHandler(t *testing.T) {
+	// Launch a separate process we will send SIGINT to.
+	bin, err := os.Executable()
+	require.NoError(t, err)
+	cmd := exec.Command(bin)
+
+	// buffers used to capture the output of the child process.
+	so, _ := cmd.StdoutPipe()
+	se, _ := cmd.StderrPipe()
+
+	cmd.Env = []string{"VerifySignalHandler=1"}
+	// CREATE_NEW_PROCESS_GROUP is required to send SIGINT to
+	// the child process.
+	cmd.SysProcAttr = &syscall.SysProcAttr{
+		CreationFlags: windows.CREATE_NEW_PROCESS_GROUP,
+	}
+	err = cmd.Start()
+	require.NoError(t, err)
+	childPid := cmd.Process.Pid
+
+	c := make(chan struct{}, 1)
+
+	go func() {
+		se.Read(make([]byte, 1024))
+		c <- struct{}{}
+	}()
+
+	// block until child proc is ready.
+	<-c
+
+	// Send signal to child proc.
+	err = windows.GenerateConsoleCtrlEvent(1, uint32(childPid))
+	require.NoError(t, err)
+
+	// Drain out stdout/stderr before waiting.
+	buf := new(bytes.Buffer)
+	buf.ReadFrom(se)
+	buf.ReadFrom(so)
+
+	// Wait till child proc finishes
+	err = cmd.Wait()
+
+	// stdout should have ONSTOP printed on it from OnStop handler.
+	assert.Contains(t, buf.String(), "ONSTOP")
+	assert.NoError(t, err)
+}