diff --git a/agent/Cargo.lock b/agent/Cargo.lock
index e3dbe64..ed11bf6 100644
--- a/agent/Cargo.lock
+++ b/agent/Cargo.lock
@@ -276,6 +276,15 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "envy"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "errno"
 version = "0.3.1"
@@ -886,11 +895,15 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "clap",
+ "envy",
  "nix",
+ "once_cell",
  "prost",
  "reqwest",
+ "serde",
  "serde_json",
  "sysinfo",
+ "tempfile",
  "tokio",
  "tokio-stream",
  "tonic",
@@ -1118,9 +1131,23 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.171"
+version = "1.0.173"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
+checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.173"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.26",
+]
 
 [[package]]
 name = "serde_json"
diff --git a/agent/Cargo.toml b/agent/Cargo.toml
index 90e7fd7..6be735b 100644
--- a/agent/Cargo.toml
+++ b/agent/Cargo.toml
@@ -6,11 +6,15 @@ edition = "2021"
 [dependencies]
 anyhow = "1.0.71"
 clap = { version = "4.3.9" }
+envy = "0.4.2"
 nix = "0.26.2"
+once_cell = "1.18.0"
 prost = "0.11.9"
-reqwest = { version = "0.11.18", features = ["blocking", "rustls-tls"], default-features = false }
-serde_json = "1.0.99"
+reqwest = { version = "0.11.18", features = ["blocking", "rustls-tls", "json"], default-features = false }
+serde = { version = "1.0.173", features = ["derive"] }
+serde_json = "1.0.103"
 sysinfo = { version = "0.29.2", default-features = false }
+tempfile = "3.6.0"
 tokio = { version = "1.28.2", features = ["rt-multi-thread", "io-util", "process", "macros", "signal"] }
 tokio-stream = { version = "0.1.14", features = ["net"] }
 tonic = { version = "0.9.2", features = ["tls"] }
diff --git a/agent/src/bin/prymn_agent.rs b/agent/src/bin/prymn_agent.rs
index 303d656..77d1c47 100644
--- a/agent/src/bin/prymn_agent.rs
+++ b/agent/src/bin/prymn_agent.rs
@@ -1,4 +1,4 @@
-use anyhow::anyhow;
+use anyhow::Context;
 use clap::arg;
 use prymn_agent::{self_update, server};
 
@@ -18,7 +18,7 @@ fn main() -> anyhow::Result<()> {
         server::main()
     } else if let Some(token) = command.get_one::<String>("install") {
         tracing::info!("starting installation...");
-        self_update::install(token)
+        self_update::install(token).context("failed to install the agent to the system")
     } else {
         unreachable!()
     }
diff --git a/agent/src/config.rs b/agent/src/config.rs
new file mode 100644
index 0000000..3861de5
--- /dev/null
+++ b/agent/src/config.rs
@@ -0,0 +1,18 @@
+use once_cell::sync::Lazy;
+use serde::Deserialize;
+
+#[derive(Deserialize, Debug)]
+pub struct Config {
+    #[serde(default = "default_backend_url")]
+    pub backend_url: String,
+}
+
+fn default_backend_url() -> String {
+    "https://prymn.net".to_string()
+}
+
+pub static CONFIG: Lazy<Config> =
+    Lazy::new(|| match envy::prefixed("PRYMN_").from_env::<Config>() {
+        Ok(config) => config,
+        Err(_) => todo!("handle this error"),
+    });
diff --git a/agent/src/lib.rs b/agent/src/lib.rs
index 0094ae6..a65f914 100644
--- a/agent/src/lib.rs
+++ b/agent/src/lib.rs
@@ -1,2 +1,3 @@
+pub mod config;
 pub mod self_update;
 pub mod server;
diff --git a/agent/src/self_update.rs b/agent/src/self_update.rs
index b39b9a0..237a4b8 100644
--- a/agent/src/self_update.rs
+++ b/agent/src/self_update.rs
@@ -1,34 +1,32 @@
-use std::{os::unix::prelude::PermissionsExt, path::Path};
+use std::{fs::File, io::Write, os::unix::prelude::PermissionsExt, path::Path, process::Command};
 
-use anyhow::{anyhow, Context};
-use reqwest::Url;
+use anyhow::Context;
+use reqwest::{blocking::Client, StatusCode};
+use serde::Deserialize;
 
-const GET_PRYMN_URL: &str = "todo";
+use crate::config;
 
-pub fn update() -> anyhow::Result<()> {
-    let _url = {
-        let url = std::env::var("GET_PRYMN_URL").unwrap_or_else(|_| String::from(GET_PRYMN_URL));
-        Url::parse(&url)?
-    };
-
-    todo!();
-}
+const PRYMN_PATH: &str = "/usr/local/bin/prymn_agent";
 
 pub fn install(token: &str) -> anyhow::Result<()> {
     let this_exe = std::env::current_exe()?;
-    let prymn_path = Path::new("/usr/local/bin/prymn_agent");
 
-    copy_binary(&this_exe, prymn_path)?;
-    register_agent(token).context("while registering the agent")?;
-    install_service()?;
+    copy_binary(&this_exe, &Path::new(PRYMN_PATH)).with_context(|| {
+        format!(
+            "could not copy the file {} to the destination {PRYMN_PATH}",
+            this_exe.to_str().unwrap(),
+        )
+    })?;
+
+    install_service_file(&Path::new("/etc/systemd/system/prymn.service"))
+        .context("could not install the agent daemon service")?;
+
+    register_to_backend(token).context("could not register the agent")?;
 
     Ok(())
 }
 
-fn copy_binary(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> anyhow::Result<()> {
-    let src = src.as_ref();
-    let dest = dest.as_ref();
-
+fn copy_binary(src: &Path, dest: &Path) -> anyhow::Result<()> {
     if dest.exists() {
         // unlink the potentially running binary
         std::fs::remove_file(dest)?;
@@ -36,33 +34,111 @@ fn copy_binary(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> anyhow::Result<
 
     std::fs::copy(src, dest)?;
 
-    let metadata = dest.metadata()?;
-    let mut perms = metadata.permissions();
+    let mut perms = dest.metadata()?.permissions();
     perms.set_mode(0o755);
     std::fs::set_permissions(dest, perms)?;
 
     Ok(())
 }
 
-fn register_agent(token: &str) -> anyhow::Result<()> {
-    let client = reqwest::blocking::Client::new();
+fn install_service_file(dest: &Path) -> anyhow::Result<()> {
+    let mut file = File::create(dest)?;
+    write!(
+        file,
+        r#"
+[Unit]
+Description=Prymn Agent Service
+After=network.target
 
-    let res = client
-        .post("http://localhost:4000/api/v1/servers/register")
-        .body(serde_json::json!({"token": token}).to_string())
-        .send()?;
+[Service]
+ExecStart={PRYMN_PATH} -d
+Type=simple
+Restart=always
 
-    if !res.status().is_success() {
-        // TODO: Make better error message
-        return Err(anyhow!(
-            "register request returned an error: {}",
-            res.text()?
-        ));
+[Install]
+WantedBy=default.target
+"#
+    )?;
+
+    if !Command::new("systemctl")
+        .arg("daemon-reload")
+        .status()?
+        .success()
+    {
+        anyhow::bail!("command exit with non-zero exit code; could not reload systemd daemon");
+    }
+
+    if !Command::new("systemctl")
+        .arg("enable")
+        .arg("--now")
+        .arg("prymn.service")
+        .status()?
+        .success()
+    {
+        anyhow::bail!("command exit with non-zero exit code; could not enable systemd service");
     }
 
     Ok(())
 }
 
-fn install_service() -> anyhow::Result<()> {
-    Ok(())
+fn register_to_backend(token: &str) -> anyhow::Result<()> {
+    let client = Client::new();
+    let response = client
+        .post(format!(
+            "{}/api/v1/servers/register",
+            config::CONFIG.backend_url
+        ))
+        .json(&serde_json::json!({ "token": token }))
+        .send()?;
+
+    // TODO: When the backend API is established more concretely, change this to something better.
+    #[derive(Deserialize)]
+    struct ApiError {
+        errors: serde_json::Value,
+    }
+
+    match response.status() {
+        StatusCode::UNPROCESSABLE_ENTITY => {
+            let error = response.json::<ApiError>()?;
+            anyhow::bail!(
+                "request was unsuccessful: the backend received invalid data: {}",
+                error.errors.to_string()
+            )
+        }
+        status if !status.is_success() => {
+            anyhow::bail!("request was unsuccessful: error {}", status)
+        }
+        _ => Ok(()),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use tempfile::*;
+
+    use super::*;
+
+    #[test]
+    fn copy_binary_works() {
+        let temp_dir = tempdir().unwrap();
+        let file1_path = temp_dir.path().join("file1");
+        let mut file1 = File::create(&file1_path).unwrap();
+        let file2_path = temp_dir.path().join("file2");
+        let mut file2 = File::create(&file2_path).unwrap();
+
+        writeln!(file1, "old data").unwrap();
+        writeln!(file2, "new data").unwrap();
+
+        copy_binary(&file2_path, &file1_path).expect("could not copy file");
+
+        let perms = file1_path
+            .metadata()
+            .expect("could not retrieve metadata")
+            .permissions();
+
+        let new_data = std::fs::read_to_string(file1_path).unwrap();
+
+        assert_eq!(new_data, "new data\n");
+        assert!(perms.mode() & 0o755 == 0o755);
+    }
 }
diff --git a/app/lib/prymn_web/controllers/server_controller.ex b/app/lib/prymn_web/controllers/server_controller.ex
index 48b548b..4a1699f 100644
--- a/app/lib/prymn_web/controllers/server_controller.ex
+++ b/app/lib/prymn_web/controllers/server_controller.ex
@@ -16,17 +16,17 @@ defmodule PrymnWeb.ServerController do
       {:error, :invalid_ip} ->
         Logger.error("could not register a server because we received an invalid ip")
 
-        put_status(conn, 500)
+        put_status(conn, 422)
         |> json(%{"errors" => ["invalid ip received"]})
 
       {:error, :bad_token} ->
-        put_status(conn, 400)
+        put_status(conn, 422)
         |> json(%{"errors" => %{"token" => "token is not valid"}})
 
       {:error, %Ecto.Changeset{} = changeset} ->
         errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _} -> msg end)
 
-        put_status(conn, 400)
+        put_status(conn, 422)
         |> json(%{"errors" => errors})
 
       {:error, error} ->