diff --git a/src/init.rs b/src/init.rs
index 636a7ba..5635b78 100644
--- a/src/init.rs
+++ b/src/init.rs
@@ -4,9 +4,15 @@ use async_rwlock::RwLock;
 use lazy_static::lazy_static;
 use log4rs::Handle;
 use lru::LruCache;
+use signal_hook::consts::SIGINT;
 use signal_hook::{consts::SIGHUP, iterator::Signals};
-use std::num::NonZero;
-use std::{env::set_current_dir, io, num::NonZeroUsize, sync::Arc, thread};
+use std::{
+    fs,
+    num::NonZero,
+    path::PathBuf,
+    process,
+    {env::set_current_dir, io, num::NonZeroUsize, sync::Arc, thread},
+};
 
 #[cfg(feature = "log")]
 use {
@@ -20,6 +26,7 @@ use {
 };
 
 lazy_static! {
+    pub static ref PID_FILE: Mutex<Option<PathBuf>> = Mutex::new(None);
     pub static ref T: Arc<RwLock<Option<i32>>> = Arc::new(RwLock::new(None));
     pub static ref LOGGER_HANDLE: Mutex<Option<Handle>> = Mutex::new(None);
 }
@@ -146,7 +153,7 @@ where
 }
 
 pub async fn init_signal() -> io::Result<()> {
-    let mut signals = Signals::new([SIGHUP])?;
+    let mut signals = Signals::new([SIGHUP, SIGINT])?;
 
     tokio::spawn(async move {
         for sig in signals.forever() {
@@ -182,6 +189,9 @@ pub async fn init_signal() -> io::Result<()> {
                 let mut t = T.write().await;
                 *t = None;
                 drop(t);
+            } else if sig == SIGINT {
+                fs::remove_file(PID_FILE.try_lock().unwrap().clone().unwrap().as_path()).unwrap();
+                process::exit(0);
             }
         }
     });
diff --git a/src/server.rs b/src/server.rs
index 0112282..fc060f0 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1,6 +1,6 @@
 use crate::{
     config::{Config, ARGS, CONFIG, CONFIG_PATH, DEFAULT_CONFIG, DEFAULT_INTERVAL},
-    init::{init_cache, init_signal, DATE_FORMAT, FILE_CACHE, INDEX_CACHE, T},
+    init::{init_cache, init_signal, DATE_FORMAT, FILE_CACHE, INDEX_CACHE, PID_FILE, T},
     route::{location_index, mime_match, root_relative, status_page},
 };
 
@@ -8,7 +8,13 @@ use anyhow::{Context, Result};
 use chrono::{DateTime, Utc};
 use mime::Mime;
 use std::{
-    collections::HashMap, env::set_current_dir, error::Error, ops::Deref, path::Path, sync::Arc,
+    collections::HashMap,
+    env::{self, set_current_dir},
+    error::Error,
+    fs,
+    ops::Deref,
+    path::Path,
+    sync::Arc,
 };
 
 #[cfg(feature = "log")]
@@ -313,12 +319,30 @@ pub async fn zest_main() -> Result<(), Box<dyn Error>> {
 
     set_current_dir(config.clone().server.root)?;
 
+    let runtime_dir = env::temp_dir();
+    let zest_pid = runtime_dir.join("zest.pid");
+    fs::create_dir_all(zest_pid.clone()).with_context(|| {
+        format!(
+            "failed to create dir {}",
+            zest_pid.as_path().to_str().unwrap()
+        )
+    })?;
+    let pid_file = zest_pid.clone().join(std::process::id().to_string());
+    *PID_FILE.try_lock().unwrap() = Some(pid_file.clone());
+
+    File::create(pid_file.clone()).await.with_context(|| {
+        format!(
+            "failed to create file {}",
+            pid_file.as_path().to_str().unwrap()
+        )
+    })?;
+
     #[cfg(feature = "log")]
     init_logger(&config.clone())
         .await
         .context("failed to init logger")?;
 
-    init_signal().await.context("failed to init signal")?;
+    init_signal().await.context("failed to init signal hook")?;
 
     #[cfg(feature = "lru_cache")]
     init_cache().await.context("failed to init lru cache")?;