diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go
index 69a3ad46..d2be0f36 100644
--- a/adapter/inbound/manager.go
+++ b/adapter/inbound/manager.go
@@ -44,7 +44,7 @@ func (m *Manager) Start(stage adapter.StartStage) error {
 	for _, inbound := range m.inbounds {
 		err := adapter.LegacyStart(inbound, stage)
 		if err != nil {
-			return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
+			return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
 		}
 	}
 	return nil
@@ -118,7 +118,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
 		for _, stage := range adapter.ListStartStages {
 			err = adapter.LegacyStart(inbound, stage)
 			if err != nil {
-				return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
+				return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
 			}
 		}
 	}
diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go
index 7a1639dc..aff9fadb 100644
--- a/adapter/lifecycle.go
+++ b/adapter/lifecycle.go
@@ -1,5 +1,7 @@
 package adapter
 
+import E "github.com/sagernet/sing/common/exceptions"
+
 type StartStage uint8
 
 const (
@@ -16,7 +18,7 @@ var ListStartStages = []StartStage{
 	StartStateStarted,
 }
 
-func (s StartStage) Action() string {
+func (s StartStage) String() string {
 	switch s {
 	case StartStateInitialize:
 		return "initialize"
@@ -25,7 +27,7 @@ func (s StartStage) Action() string {
 	case StartStatePostStart:
 		return "post-start"
 	case StartStateStarted:
-		return "start-after-started"
+		return "finish-start"
 	default:
 		panic("unknown stage")
 	}
@@ -40,3 +42,23 @@ type LifecycleService interface {
 	Name() string
 	Lifecycle
 }
+
+func Start(stage StartStage, services ...Lifecycle) error {
+	for _, service := range services {
+		err := service.Start(stage)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func StartNamed(stage StartStage, services []LifecycleService) error {
+	for _, service := range services {
+		err := service.Start(stage)
+		if err != nil {
+			return E.Cause(err, stage.String(), " ", service.Name())
+		}
+	}
+	return nil
+}
diff --git a/adapter/lifecycle_legacy.go b/adapter/lifecycle_legacy.go
index f73f9234..0c8c75da 100644
--- a/adapter/lifecycle_legacy.go
+++ b/adapter/lifecycle_legacy.go
@@ -14,7 +14,7 @@ func LegacyStart(starter any, stage StartStage) error {
 		}); isStarter {
 			return starter.Start()
 		}
-	case StartStatePostStart:
+	case StartStateStarted:
 		if postStarter, isPostStarter := starter.(interface {
 			PostStart() error
 		}); isPostStarter {
diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go
index 10a89a1c..84a105c5 100644
--- a/adapter/outbound/manager.go
+++ b/adapter/outbound/manager.go
@@ -61,7 +61,7 @@ func (m *Manager) Start(stage adapter.StartStage) error {
 		for _, outbound := range outbounds {
 			err := adapter.LegacyStart(outbound, stage)
 			if err != nil {
-				return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
+				return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
 			}
 		}
 	}
@@ -234,7 +234,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
 		for _, stage := range adapter.ListStartStages {
 			err = adapter.LegacyStart(outbound, stage)
 			if err != nil {
-				return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
+				return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
 			}
 		}
 	}
diff --git a/box.go b/box.go
index 6205a7d9..d9abe15a 100644
--- a/box.go
+++ b/box.go
@@ -315,27 +315,17 @@ func (s *Box) preStart() error {
 	if err != nil {
 		return E.Cause(err, "start logger")
 	}
-	for _, lifecycleService := range s.services {
-		err = lifecycleService.Start(adapter.StartStateInitialize) // cache-file
-		if err != nil {
-			return E.Cause(err, "initialize ", lifecycleService.Name())
-		}
+	err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
+	if err != nil {
+		return err
 	}
-	for _, lifecycle := range []adapter.Lifecycle{
-		s.network, s.router, s.outbound, s.inbound,
-	} {
-		err = lifecycle.Start(adapter.StartStateInitialize)
-		if err != nil {
-			return err
-		}
+	err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound)
+	if err != nil {
+		return err
 	}
-	for _, lifecycle := range []adapter.Lifecycle{
-		s.outbound, s.network, s.router,
-	} {
-		err = lifecycle.Start(adapter.StartStateStart)
-		if err != nil {
-			return err
-		}
+	err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router)
+	if err != nil {
+		return err
 	}
 	return nil
 }
@@ -345,31 +335,29 @@ func (s *Box) start() error {
 	if err != nil {
 		return err
 	}
-	for _, lifecycleService := range s.services {
-		err = lifecycleService.Start(adapter.StartStateStart)
-		if err != nil {
-			return E.Cause(err, "initialize ", lifecycleService.Name())
-		}
+	err = adapter.StartNamed(adapter.StartStateStart, s.services)
+	if err != nil {
+		return err
 	}
 	err = s.inbound.Start(adapter.StartStateStart)
 	if err != nil {
 		return err
 	}
-	for _, lifecycleService := range []adapter.Lifecycle{
-		s.outbound, s.network, s.router, s.inbound,
-	} {
-		err = lifecycleService.Start(adapter.StartStatePostStart)
-		if err != nil {
-			return err
-		}
+	err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound)
+	if err != nil {
+		return err
 	}
-	for _, lifecycleService := range []adapter.Lifecycle{
-		s.network, s.router, s.outbound, s.inbound,
-	} {
-		err = lifecycleService.Start(adapter.StartStateStarted)
-		if err != nil {
-			return err
-		}
+	err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
+	if err != nil {
+		return err
+	}
+	err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound)
+	if err != nil {
+		return err
+	}
+	err = adapter.StartNamed(adapter.StartStateStarted, s.services)
+	if err != nil {
+		return err
 	}
 	return nil
 }
diff --git a/go.mod b/go.mod
index bf88f1d3..0a2f7b0c 100644
--- a/go.mod
+++ b/go.mod
@@ -25,14 +25,14 @@ require (
 	github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3
 	github.com/sagernet/quic-go v0.48.1-beta.1
 	github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
-	github.com/sagernet/sing v0.6.0-alpha.3
+	github.com/sagernet/sing v0.6.0-alpha.4
 	github.com/sagernet/sing-dns v0.4.0-alpha.1
 	github.com/sagernet/sing-mux v0.3.0-alpha.1
 	github.com/sagernet/sing-quic v0.3.0-rc.2
 	github.com/sagernet/sing-shadowsocks v0.2.7
 	github.com/sagernet/sing-shadowsocks2 v0.2.0
 	github.com/sagernet/sing-shadowtls v0.1.4
-	github.com/sagernet/sing-tun v0.6.0-alpha.3
+	github.com/sagernet/sing-tun v0.6.0-alpha.4
 	github.com/sagernet/sing-vmess v0.1.12
 	github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
 	github.com/sagernet/utls v1.6.7
@@ -53,7 +53,9 @@ require (
 	howett.net/plist v1.0.1
 )
 
-//replace github.com/sagernet/sing => ../sing
+replace github.com/sagernet/sing => ../sing
+
+replace github.com/sagernet/sing-tun => ../sing-tun
 
 require (
 	github.com/ajg/form v1.5.1 // indirect
diff --git a/go.sum b/go.sum
index 3b789c9b..63709752 100644
--- a/go.sum
+++ b/go.sum
@@ -109,9 +109,6 @@ github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AE
 github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
-github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
-github.com/sagernet/sing v0.6.0-alpha.3 h1:GLp9d6Gbt+Ioeplauuzojz1nY2J6moceVGYIOv/h5gA=
-github.com/sagernet/sing v0.6.0-alpha.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
 github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg=
 github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM=
 github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
@@ -124,8 +121,6 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
 github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
 github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
 github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
-github.com/sagernet/sing-tun v0.6.0-alpha.3 h1:KddmFF9ZYC1n+HZzpAZ1StMC5v38ir6hH0PJ7uGpAGE=
-github.com/sagernet/sing-tun v0.6.0-alpha.3/go.mod h1:R/UaKB1oFIvAeMIH2btQCaDVsfCNomEjfYBSCSA94sw=
 github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=
 github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=
 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@@ -141,8 +136,14 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
@@ -181,7 +182,7 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
 golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=