diff --git a/.gitignore b/.gitignore
index 495c7004..329c2449 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .idea
+.sandbox
 include/
 .sandbox/
 .tool-versions
diff --git a/.golangci.yaml b/.golangci.yaml
index 75c0664a..bc9112fb 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -1,11 +1,12 @@
-concurrency: 16
 run:
-  deadline: 10m
+  timeout: 10m
+  concurrency: 16
   issues-exit-code: 1
   tests: true
 
 output:
-  format: colored-line-number
+  formats:
+    - format: colored-line-number
 
 linters-settings:
   godox:
@@ -17,25 +18,12 @@ issues:
 
 linters:
   enable:
-    - scopelint
-    - govet
-    - ineffassign
-    - varcheck
-    - unused
-    - deadcode
-    - bodyclose
-    - structcheck
-    - staticcheck
-    - godox
-    - rowserrcheck
-    - prealloc
     - asasalint
     - asciicheck
     - bidichk
     - bodyclose
     - containedctx
     - contextcheck
-    - deadcode
     - decorder
     - dogsled
     - dupword
@@ -86,11 +74,9 @@ linters:
     - protogetter
     - reassign
     - rowserrcheck
-    - scopelint
     - sloglint
     - sqlclosecheck
     - staticcheck
-    - structcheck
     - tagalign
     - tagliatelle
     - tenv
@@ -103,7 +89,6 @@ linters:
     - unparam
     - unused
     - usestdlibvars
-    - varcheck
     - wastedassign
     - whitespace
     - zerologlint
diff --git a/Makefile b/Makefile
index 9c52e7e5..f15fda0c 100644
--- a/Makefile
+++ b/Makefile
@@ -10,4 +10,13 @@ test:
 lint:
 	GOOS=darwin GOARCH=arm64 gomodrun golangci-lint run  ./cmd/... ./src/... --verbose
 	GOOS=linux GOARCH=amd64 gomodrun golangci-lint run  ./cmd/... ./src/... --verbose
-	GOOS=windows GOARCH=amd64 gomodrun golangci-lint run  ./cmd/... ./src/... --verbose
\ No newline at end of file
+	GOOS=windows GOARCH=amd64 gomodrun golangci-lint run  ./cmd/... ./src/... --verbose
+
+build-for-windows-amd:
+	 GOOS=windows GOARCH=amd64 go build -o bin/zcli.exe cmd/zcli/main.go
+
+build-for-linux-amd:
+	 GOOS=linux GOARCH=amd64 go build -o bin/zcli cmd/zcli/main.go
+
+build-for-darwin-arm:
+	 GOOS=darwin GOARCH=arm64 go build -o bin/zcli cmd/zcli/main.go
diff --git a/src/archiveClient/handler_findGitFiles.go b/src/archiveClient/handler_findGitFiles.go
index c208449b..f43cdb71 100644
--- a/src/archiveClient/handler_findGitFiles.go
+++ b/src/archiveClient/handler_findGitFiles.go
@@ -3,9 +3,9 @@ package archiveClient
 import (
 	"bufio"
 	"bytes"
+	"context"
 	"io"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 
@@ -13,7 +13,7 @@ import (
 	"github.com/zeropsio/zcli/src/cmdRunner"
 )
 
-func (h *Handler) FindGitFiles(workingDir string) (res []File, _ error) {
+func (h *Handler) FindGitFiles(ctx context.Context, workingDir string) (res []File, _ error) {
 	workingDir, err := filepath.Abs(workingDir)
 	if err != nil {
 		return nil, err
@@ -28,8 +28,8 @@ func (h *Handler) FindGitFiles(workingDir string) (res []File, _ error) {
 		}
 	}
 
-	createCmd := func(name string, arg ...string) *exec.Cmd {
-		cmd := exec.Command(name, arg...)
+	createCmd := func(name string, arg ...string) *cmdRunner.ExecCmd {
+		cmd := cmdRunner.CommandContext(ctx, name, arg...)
 		cmd.Dir = workingDir
 		return cmd
 	}
@@ -102,7 +102,7 @@ func (h *Handler) FindGitFiles(workingDir string) (res []File, _ error) {
 	return res, nil
 }
 
-func (h *Handler) listFiles(cmd *exec.Cmd, fn func(path string) error) error {
+func (h *Handler) listFiles(cmd *cmdRunner.ExecCmd, fn func(path string) error) error {
 	output, err := cmdRunner.Run(cmd)
 	if err != nil {
 		return err
diff --git a/src/cmd/serviceDeploy.go b/src/cmd/serviceDeploy.go
index 6c4a8240..92651df4 100644
--- a/src/cmd/serviceDeploy.go
+++ b/src/cmd/serviceDeploy.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"io"
 	"os"
-	"path"
+	"path/filepath"
 	"time"
 
 	"github.com/zeropsio/zcli/src/archiveClient"
@@ -93,7 +93,7 @@ func serviceDeployCmd() *cmdBuilder.Cmd {
 							size = s.Size()
 							reader = packageFile
 						} else {
-							tempFile := path.Join(os.TempDir(), appVersion.Id.Native())
+							tempFile := filepath.Join(os.TempDir(), appVersion.Id.Native())
 							f, err := os.Create(tempFile)
 							if err != nil {
 								return err
@@ -178,9 +178,9 @@ func serviceDeployCmd() *cmdBuilder.Cmd {
 				cmdData.UxBlocks,
 				[]uxHelpers.Process{{
 					F:                   uxHelpers.CheckZeropsProcess(deployProcess.Id, cmdData.RestApiClient),
-					RunningMessage:      i18n.T(i18n.PushRunning),
-					ErrorMessageMessage: i18n.T(i18n.PushFailed),
-					SuccessMessage:      i18n.T(i18n.PushFinished),
+					RunningMessage:      i18n.T(i18n.DeployRunning),
+					ErrorMessageMessage: i18n.T(i18n.DeployFailed),
+					SuccessMessage:      i18n.T(i18n.DeployFinished),
 				}},
 			)
 
diff --git a/src/cmd/servicePush.go b/src/cmd/servicePush.go
index 804fef35..a2fa157b 100644
--- a/src/cmd/servicePush.go
+++ b/src/cmd/servicePush.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"io"
 	"os"
-	"path"
+	"path/filepath"
 	"time"
 
 	"github.com/zeropsio/zcli/src/archiveClient"
@@ -92,13 +92,13 @@ func servicePushCmd() *cmdBuilder.Cmd {
 							size = s.Size()
 							reader = packageFile
 						} else {
-							tempFile := path.Join(os.TempDir(), appVersion.Id.Native())
+							tempFile := filepath.Join(os.TempDir(), appVersion.Id.Native())
 							f, err := os.Create(tempFile)
 							if err != nil {
 								return err
 							}
 							defer os.Remove(tempFile)
-							files, err := arch.FindGitFiles(cmdData.Params.GetString("workingDir"))
+							files, err := arch.FindGitFiles(ctx, cmdData.Params.GetString("workingDir"))
 							if err != nil {
 								return err
 							}
diff --git a/src/cmd/vpnDown.go b/src/cmd/vpnDown.go
index 6541892f..744cdacb 100644
--- a/src/cmd/vpnDown.go
+++ b/src/cmd/vpnDown.go
@@ -42,7 +42,7 @@ func disconnectVpn(ctx context.Context, uxBlocks uxBlock.UxBlocks) error {
 	}
 	defer f.Close()
 
-	c := wg.DownCmd(ctx, filePath)
+	c := wg.DownCmd(ctx, filePath, constants.WgInterfaceName)
 	_, err = cmdRunner.Run(c)
 	if err != nil {
 		return err
diff --git a/src/cmd/vpnUp.go b/src/cmd/vpnUp.go
index 04aa61aa..1bc2fce8 100644
--- a/src/cmd/vpnUp.go
+++ b/src/cmd/vpnUp.go
@@ -6,7 +6,6 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
-	"github.com/zeropsio/zcli/src/uxBlock"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 
 	"github.com/zeropsio/zcli/src/cliStorage"
@@ -18,6 +17,7 @@ import (
 	"github.com/zeropsio/zcli/src/file"
 	"github.com/zeropsio/zcli/src/i18n"
 	"github.com/zeropsio/zcli/src/nettools"
+	"github.com/zeropsio/zcli/src/uxBlock"
 	"github.com/zeropsio/zcli/src/uxBlock/styles"
 	"github.com/zeropsio/zcli/src/uxHelpers"
 	"github.com/zeropsio/zcli/src/wg"
@@ -96,6 +96,7 @@ func vpnUpCmd() *cmdBuilder.Cmd {
 			if err != nil {
 				return err
 			}
+			defer f.Close()
 
 			err = wg.GenerateConfig(f, privateKey, vpnSettings)
 			if err != nil {
diff --git a/src/cmdRunner/execCmd.go b/src/cmdRunner/execCmd.go
new file mode 100644
index 00000000..598273a6
--- /dev/null
+++ b/src/cmdRunner/execCmd.go
@@ -0,0 +1,47 @@
+package cmdRunner
+
+import (
+	"context"
+	"os/exec"
+)
+
+type Func func(ctx context.Context) error
+
+func CommandContext(ctx context.Context, cmd string, args ...string) *ExecCmd {
+	return &ExecCmd{
+		Cmd: exec.CommandContext(ctx, cmd, args...),
+		ctx: ctx,
+	}
+}
+
+type ExecCmd struct {
+	*exec.Cmd
+	//nolint:containedctx
+	ctx    context.Context
+	before Func
+	after  Func
+}
+
+func (e *ExecCmd) SetBefore(f Func) *ExecCmd {
+	e.before = f
+	return e
+}
+
+func (e *ExecCmd) execBefore() error {
+	if e.before == nil {
+		return nil
+	}
+	return e.before(e.ctx)
+}
+
+func (e *ExecCmd) SetAfter(f Func) *ExecCmd {
+	e.after = f
+	return e
+}
+
+func (e *ExecCmd) execAfter() error {
+	if e.after == nil {
+		return nil
+	}
+	return e.after(e.ctx)
+}
diff --git a/src/cmdRunner/run.go b/src/cmdRunner/run.go
index e88dcd8f..69290dcc 100644
--- a/src/cmdRunner/run.go
+++ b/src/cmdRunner/run.go
@@ -12,13 +12,8 @@ var ErrIpAlreadySet = errors.New("RTNETLINK answers: File exists")
 var ErrCannotFindDevice = errors.New(`Cannot find device "wg0"`)
 var ErrOperationNotPermitted = errors.New(`Operation not permitted`)
 
-type ExecErrInterface interface {
-	error
-	ExitCode() int
-}
-
 type execError struct {
-	cmd      *exec.Cmd
+	cmd      *ExecCmd
 	prev     error
 	exitCode int
 }
@@ -39,13 +34,17 @@ func (e execError) Is(target error) bool {
 	return errors.Is(e.prev, target)
 }
 
-func Run(cmd *exec.Cmd) ([]byte, ExecErrInterface) {
+func Run(cmd *ExecCmd) ([]byte, error) {
 	output := &bytes.Buffer{}
 	errOutput := &bytes.Buffer{}
 	cmd.Stdout = output
 	cmd.Stderr = errOutput
 	cmd.Env = append(os.Environ(), cmd.Env...)
 
+	if err := cmd.execBefore(); err != nil {
+		return nil, err
+	}
+
 	if err := cmd.Run(); err != nil {
 		exitCode := 0
 		var exitError *exec.ExitError
@@ -81,5 +80,9 @@ func Run(cmd *exec.Cmd) ([]byte, ExecErrInterface) {
 		return nil, execError
 	}
 
+	if err := cmd.execAfter(); err != nil {
+		return nil, err
+	}
+
 	return output.Bytes(), nil
 }
diff --git a/src/constants/darwin.go b/src/constants/darwin.go
index 6e043cb9..0dde89dc 100644
--- a/src/constants/darwin.go
+++ b/src/constants/darwin.go
@@ -5,7 +5,7 @@ package constants
 
 import (
 	"os"
-	"path"
+	"path/filepath"
 )
 
 func getDataFilePathsReceivers() []pathReceiver {
@@ -21,7 +21,7 @@ func getDataFilePathsReceivers() []pathReceiver {
 func getLogFilePathReceivers() []pathReceiver {
 	return []pathReceiver{
 		receiverFromEnv(CliLogFilePathEnvVar),
-		receiverFromPath(path.Join("/usr/local/var/log/", ZeropsLogFile)),
+		receiverFromPath(filepath.Join("/usr/local/var/log/", ZeropsLogFile)),
 		receiverFromOsFunc(os.UserConfigDir, ZeropsDir, ZeropsLogFile),
 		receiverFromOsFunc(os.UserHomeDir, ZeropsDir, ZeropsLogFile),
 		receiverFromOsFunc(os.UserHomeDir, "zerops."+ZeropsLogFile),
@@ -32,9 +32,9 @@ func getLogFilePathReceivers() []pathReceiver {
 func getWgConfigFilePathReceivers() []pathReceiver {
 	return []pathReceiver{
 		receiverFromEnv(CliWgConfigPathEnvVar),
-		receiverFromPath(path.Join("/etc/wireguard/", WgConfigFile)),
-		receiverFromPath(path.Join("/usr/local/etc/wireguard/", WgConfigFile)),
-		receiverFromPath(path.Join("/opt/homebrew/etc/wireguard/", WgConfigFile)),
+		receiverFromPath(filepath.Join("/etc/wireguard/", WgConfigFile)),
+		receiverFromPath(filepath.Join("/usr/local/etc/wireguard/", WgConfigFile)),
+		receiverFromPath(filepath.Join("/opt/homebrew/etc/wireguard/", WgConfigFile)),
 		receiverFromOsFunc(os.UserConfigDir, ZeropsDir, WgConfigFile),
 		receiverFromOsFunc(os.UserHomeDir, ZeropsDir, WgConfigFile),
 		receiverFromOsFunc(os.UserHomeDir, WgConfigFile),
diff --git a/src/constants/linux.go b/src/constants/linux.go
index 26fc412f..dc448eb2 100644
--- a/src/constants/linux.go
+++ b/src/constants/linux.go
@@ -5,7 +5,7 @@ package constants
 
 import (
 	"os"
-	"path"
+	"path/filepath"
 )
 
 func getDataFilePathsReceivers() []pathReceiver {
@@ -21,7 +21,7 @@ func getDataFilePathsReceivers() []pathReceiver {
 func getLogFilePathReceivers() []pathReceiver {
 	return []pathReceiver{
 		receiverFromEnv(CliLogFilePathEnvVar),
-		receiverFromPath(path.Join("/var/log/", ZeropsLogFile)),
+		receiverFromPath(filepath.Join("/var/log/", ZeropsLogFile)),
 		receiverFromOsFunc(os.UserConfigDir, ZeropsDir, ZeropsLogFile),
 		receiverFromOsFunc(os.UserHomeDir, ZeropsDir, ZeropsLogFile),
 		receiverFromOsFunc(os.UserHomeDir, "zerops."+ZeropsLogFile),
@@ -32,9 +32,9 @@ func getLogFilePathReceivers() []pathReceiver {
 func getWgConfigFilePathReceivers() []pathReceiver {
 	return []pathReceiver{
 		receiverFromEnv(CliWgConfigPathEnvVar),
-		receiverFromPath(path.Join("/etc/wireguard/", WgConfigFile)),
-		receiverFromPath(path.Join("/usr/local/etc/wireguard/", WgConfigFile)),
-		receiverFromPath(path.Join("/opt/homebrew/etc/wireguard/", WgConfigFile)),
+		receiverFromPath(filepath.Join("/etc/wireguard/", WgConfigFile)),
+		receiverFromPath(filepath.Join("/usr/local/etc/wireguard/", WgConfigFile)),
+		receiverFromPath(filepath.Join("/opt/homebrew/etc/wireguard/", WgConfigFile)),
 		receiverFromOsFunc(os.UserConfigDir, ZeropsDir, WgConfigFile),
 		receiverFromOsFunc(os.UserHomeDir, ZeropsDir, WgConfigFile),
 		receiverFromOsFunc(os.UserHomeDir, WgConfigFile),
diff --git a/src/constants/zerops.go b/src/constants/zerops.go
index 44cc94dd..ea706412 100644
--- a/src/constants/zerops.go
+++ b/src/constants/zerops.go
@@ -2,7 +2,6 @@ package constants
 
 import (
 	"os"
-	"path"
 	"path/filepath"
 	"strings"
 
@@ -17,6 +16,7 @@ const (
 	ZeropsDir             = "zerops"
 	ZeropsLogFile         = "zerops.log"
 	WgConfigFile          = "zerops.conf"
+	WgInterfaceName       = "zerops"
 	CliDataFileName       = "cli.data"
 	CliDataFilePathEnvVar = "ZEROPS_CLI_DATA_FILE_PATH"
 	CliLogFilePathEnvVar  = "ZEROPS_CLI_LOG_FILE_PATH"
@@ -35,7 +35,7 @@ func LogFilePath() (string, os.FileMode, error) {
 }
 
 func WgConfigFilePath() (string, os.FileMode, error) {
-	return checkReceivers(getWgConfigFilePathReceivers(), 0600, i18n.UnableToWriteLogFile)
+	return checkReceivers(getWgConfigFilePathReceivers(), 0600, i18n.UnableToWriteWgConfigFile)
 }
 
 func checkReceivers(pathReceivers []pathReceiver, fileMode os.FileMode, errorText string) (string, os.FileMode, error) {
@@ -96,7 +96,7 @@ func findFirstWritablePath(paths []pathReceiver, fileMode os.FileMode) string {
 }
 
 func checkPath(filePath string, fileMode os.FileMode) (string, error) {
-	dir := path.Dir(filePath)
+	dir := filepath.Dir(filePath)
 
 	if err := os.MkdirAll(dir, 0755); err != nil {
 		return "", err
diff --git a/src/i18n/en.go b/src/i18n/en.go
index f8791231..ba447a15 100644
--- a/src/i18n/en.go
+++ b/src/i18n/en.go
@@ -106,6 +106,9 @@ var en = map[string]string{
 		"directory. The working directory is by default the current directory and can be changed\n" +
 		"using the --workingDir flag. zCLI deploys selected directories and/or files to Zerops. \n\n" +
 		"To build your application in Zerops, use the zcli push command instead.",
+	DeployRunning:  "Deploy is running",
+	DeployFailed:   "Deploy failed",
+	DeployFinished: "Deploy finished",
 
 	// push
 	CmdHelpPush: "the service push command.",
@@ -242,10 +245,11 @@ var en = map[string]string{
 	CliLogFilePathEnvVar:  "Path to a log file.",
 	CliDataFilePathEnvVar: "Path to data file.",
 
-	UnknownTerminalMode:    "Unknown terminal mode: %s. Falling back to auto-discovery. Possible values: auto, enabled, disabled.",
-	UnableToDecodeJsonFile: "Unable to decode json file: %s",
-	UnableToWriteCliData:   "Unable to write zcli data, paths tested: %s",
-	UnableToWriteLogFile:   "Unable to write zcli debug log file, paths tested: %s",
+	UnknownTerminalMode:       "Unknown terminal mode: %s. Falling back to auto-discovery. Possible values: auto, enabled, disabled.",
+	UnableToDecodeJsonFile:    "Unable to decode json file: %s",
+	UnableToWriteCliData:      "Unable to write zcli data, paths tested: %s",
+	UnableToWriteLogFile:      "Unable to write zcli debug log file, paths tested: %s",
+	UnableToWriteWgConfigFile: "Unable to write zcli wireguard config file, paths tested: %s",
 
 	// args
 	ArgsOnlyOneOptionalAllowed: "optional arg %s can be only the last one",
diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go
index f982f2ac..d36b404a 100644
--- a/src/i18n/i18n.go
+++ b/src/i18n/i18n.go
@@ -111,6 +111,9 @@ const (
 	CmdHelpServiceDeploy = "CmdHelpServiceDeploy"
 	CmdDescDeploy        = "CmdDescDeploy"
 	CmdDescDeployLong    = "CmdDescDeployLong"
+	DeployRunning        = "DeployRunning"
+	DeployFailed         = "DeployFailed"
+	DeployFinished       = "DeployFinished"
 
 	// push
 	CmdHelpPush     = "CmdHelpPush"
@@ -236,10 +239,11 @@ const (
 	CliLogFilePathEnvVar  = "CliLogFilePathEnvVar"
 	CliDataFilePathEnvVar = "CliDataFilePathEnvVar"
 
-	UnknownTerminalMode    = "UnknownTerminalMode"
-	UnableToDecodeJsonFile = "UnableToDecodeJsonFile"
-	UnableToWriteCliData   = "UnableToWriteCliData"
-	UnableToWriteLogFile   = "UnableToWriteLogFile"
+	UnknownTerminalMode       = "UnknownTerminalMode"
+	UnableToDecodeJsonFile    = "UnableToDecodeJsonFile"
+	UnableToWriteCliData      = "UnableToWriteCliData"
+	UnableToWriteLogFile      = "UnableToWriteLogFile"
+	UnableToWriteWgConfigFile = "UnableToWriteWgConfigFile"
 
 	// args
 	ArgsOnlyOneOptionalAllowed = "ArgsOnlyOneOptionalAllowed"
diff --git a/src/storage/exists.go b/src/storage/exists.go
deleted file mode 100644
index ca85b772..00000000
--- a/src/storage/exists.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package storage
-
-import (
-	"os"
-)
-
-func FileExists(path string) (bool, error) {
-	f, err := os.Stat(path)
-	if err == nil && !f.IsDir() {
-		return true, nil
-	}
-	if os.IsNotExist(err) {
-		return false, nil
-	}
-	return false, err
-}
diff --git a/src/storage/handler.go b/src/storage/handler.go
index 2208a1d3..2110ceac 100644
--- a/src/storage/handler.go
+++ b/src/storage/handler.go
@@ -34,11 +34,17 @@ func New[T any](config Config) (*Handler[T], error) {
 }
 
 func (h *Handler[T]) load() error {
-	storageFileExists, err := FileExists(h.config.FilePath)
+	fileInfo, err := os.Stat(h.config.FilePath)
+	if os.IsNotExist(err) {
+		return nil
+	}
 	if err != nil {
 		return errors.WithStack(err)
 	}
-	if !storageFileExists {
+	if fileInfo.Size() == 0 {
+		if err := os.Remove(h.config.FilePath); err != nil {
+			return errors.WithStack(err)
+		}
 		return nil
 	}
 
@@ -48,29 +54,12 @@ func (h *Handler[T]) load() error {
 	}
 	defer f.Close()
 
-	// If the file is empty, set the default value and save it.
-	fi, err := f.Stat()
-	if err != nil {
-		return errors.WithStack(err)
-	}
-	if fi.Size() == 0 {
-		return h.Clear()
-	}
-
 	if err := json.NewDecoder(f).Decode(&h.data); err != nil {
 		return errors.WithMessagef(err, i18n.T(i18n.UnableToDecodeJsonFile, h.config.FilePath))
 	}
-
 	return nil
 }
 
-func (h *Handler[T]) Clear() error {
-	h.lock.Lock()
-	defer h.lock.Unlock()
-	var data T
-	return h.save(data)
-}
-
 func (h *Handler[T]) Update(callback func(T) T) (T, error) {
 	h.lock.Lock()
 	defer h.lock.Unlock()
@@ -95,10 +84,11 @@ func (h *Handler[T]) save(data T) error {
 	}(); err != nil {
 		return err
 	}
+	os.Remove(h.config.FilePath)
+	defer os.Remove(h.config.FilePath + ".new")
 	if err := os.Rename(h.config.FilePath+".new", h.config.FilePath); err != nil {
 		return errors.WithStack(err)
 	}
-	os.Remove(h.config.FilePath + ".new")
 	return nil
 }
 
diff --git a/src/wg/darwin.go b/src/wg/darwin.go
index 03ceb58f..2ee9cf09 100644
--- a/src/wg/darwin.go
+++ b/src/wg/darwin.go
@@ -10,6 +10,7 @@ import (
 	"text/template"
 
 	"github.com/pkg/errors"
+	"github.com/zeropsio/zcli/src/cmdRunner"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 
 	"github.com/zeropsio/zcli/src/i18n"
@@ -34,12 +35,12 @@ func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.Proj
 	return template.Must(template.New("wg template").Parse(vpnTmpl)).Execute(f, data)
 }
 
-func UpCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
-	return exec.CommandContext(ctx, "wg-quick", "up", filePath)
+func UpCmd(ctx context.Context, filePath string) (err *cmdRunner.ExecCmd) {
+	return cmdRunner.CommandContext(ctx, "wg-quick", "up", filePath)
 }
 
-func DownCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
-	return exec.CommandContext(ctx, "wg-quick", "down", filePath)
+func DownCmd(ctx context.Context, filePath, _ string) (err *cmdRunner.ExecCmd) {
+	return cmdRunner.CommandContext(ctx, "wg-quick", "down", filePath)
 }
 
 var vpnTmpl = `
diff --git a/src/wg/linux.go b/src/wg/linux.go
index b7629a95..f2c61aa9 100644
--- a/src/wg/linux.go
+++ b/src/wg/linux.go
@@ -10,6 +10,7 @@ import (
 	"text/template"
 
 	"github.com/pkg/errors"
+	"github.com/zeropsio/zcli/src/cmdRunner"
 	"github.com/zeropsio/zcli/src/i18n"
 	"github.com/zeropsio/zerops-go/dto/output"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -33,12 +34,12 @@ func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.Proj
 	return template.Must(template.New("wg template").Parse(vpnTmpl)).Execute(f, data)
 }
 
-func UpCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
-	return exec.CommandContext(ctx, "wg-quick", "up", filePath)
+func UpCmd(ctx context.Context, filePath string) (err *cmdRunner.ExecCmd) {
+	return cmdRunner.CommandContext(ctx, "wg-quick", "up", filePath)
 }
 
-func DownCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
-	return exec.CommandContext(ctx, "wg-quick", "down", filePath)
+func DownCmd(ctx context.Context, filePath, _ string) (err *cmdRunner.ExecCmd) {
+	return cmdRunner.CommandContext(ctx, "wg-quick", "down", filePath)
 }
 
 var vpnTmpl = `
diff --git a/src/wg/windows.go b/src/wg/windows.go
index 4c7a3516..a7ae1c19 100644
--- a/src/wg/windows.go
+++ b/src/wg/windows.go
@@ -6,15 +6,24 @@ package wg
 import (
 	"context"
 	"io"
+	"os"
 	"os/exec"
+	"path/filepath"
+	"strings"
 	"text/template"
 
 	"github.com/pkg/errors"
+	"github.com/zeropsio/zcli/src/cmdRunner"
+	"github.com/zeropsio/zcli/src/constants"
 	"github.com/zeropsio/zcli/src/i18n"
 	"github.com/zeropsio/zerops-go/dto/output"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
+// To install wireguard tunnel in windows wireguard.exe has to be run with elevated permissions.
+// Only (simple) way I found to achieve this is to run Start-Process cmdlet with param '-Verb RunAS'
+// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.4
+
 func CheckWgInstallation() error {
 	_, err := exec.LookPath("wireguard")
 	if err != nil {
@@ -33,12 +42,76 @@ func GenerateConfig(f io.Writer, privateKey wgtypes.Key, vpnSettings output.Proj
 	return template.Must(template.New("wg template").Parse(vpnTmpl)).Execute(f, data)
 }
 
-func UpCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
-	return exec.CommandContext(ctx, "wireguard", "/installtunnelservice", filePath)
+func UpCmd(ctx context.Context, filePath string) (err *cmdRunner.ExecCmd) {
+	return cmdRunner.CommandContext(ctx,
+		"powershell",
+		"-Command",
+		"Start-Process", "wireguard",
+		"-Verb", "RunAs",
+		"-ArgumentList "+formatArgumentList("/installtunnelservice", filePath),
+	).
+		SetBefore(beforeUp(filePath))
 }
 
-func DownCmd(ctx context.Context, filePath string) (err *exec.Cmd) {
-	return exec.CommandContext(ctx, "wireguard", "/uninstalltunnelservice", filePath)
+// beforeUp this function tries to remove previous zerops.conf from usual wireguard configuration
+// dir (at %ProgramFiles%\WireGuard\Data\Configurations) and copy a newly generated one.
+// It fails with error = nil because it's only for windows wireguard GUI.
+func beforeUp(zeropsConfPath string) cmdRunner.Func {
+	return func(_ context.Context) error {
+		programFiles, set := os.LookupEnv("ProgramFiles")
+		if !set {
+			return nil
+		}
+
+		wgConfigDir := filepath.Join(programFiles, "WireGuard", "Data", "Configurations")
+		stat, err := os.Stat(wgConfigDir)
+		if err != nil {
+			//nolint:nilerr
+			return nil
+		}
+		if !stat.IsDir() {
+			return nil
+		}
+
+		wgConfFile := filepath.Join(wgConfigDir, constants.WgConfigFile)
+		// remove previous zerops.conf encrypted by wireguard.exe thus ending with .dpapi
+		// https://git.zx2c4.com/wireguard-windows/about/docs/enterprise.md
+		_ = os.Remove(wgConfFile + ".dpapi")
+
+		wgConf, err := os.OpenFile(filepath.Join(wgConfigDir, constants.WgConfigFile), os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			//nolint:nilerr
+			return nil
+		}
+		defer wgConf.Close()
+
+		zeropsConf, err := os.OpenFile(zeropsConfPath, os.O_RDONLY, 0666)
+		if err != nil {
+			_ = os.Remove(wgConfFile)
+			//nolint:nilerr
+			return nil
+		}
+		defer zeropsConf.Close()
+
+		_, err = io.Copy(wgConf, zeropsConf)
+		if err != nil {
+			_ = os.Remove(wgConfFile)
+			//nolint:nilerr
+			return nil
+		}
+
+		return nil
+	}
+}
+
+func DownCmd(ctx context.Context, _, interfaceName string) (err *cmdRunner.ExecCmd) {
+	return cmdRunner.CommandContext(ctx,
+		"powershell",
+		"-Command",
+		"Start-Process", "wireguard",
+		"-Verb", "RunAs",
+		"-ArgumentList "+formatArgumentList("/uninstalltunnelservice", interfaceName),
+	)
 }
 
 var vpnTmpl = `
@@ -60,3 +133,14 @@ Endpoint = {{.ProjectIpv4SharedEndpoint}}
 
 PersistentKeepalive = 5
 `
+
+func formatArgumentList(args ...string) string {
+	for i, a := range args {
+		args[i] = quote(a)
+	}
+	return strings.Join(args, ", ")
+}
+
+func quote(in string) string {
+	return `"` + in + `"`
+}