diff --git a/.envrc b/.envrc
new file mode 100644
index 00000000..3550a30f
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
index f5e9505b..19f05ce3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,3 +62,9 @@ conduit.db
 
 # Etc.
 **/*.rs.bk
+
+# Nix artifacts
+/result*
+
+# Direnv cache
+/.direnv
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
new file mode 100644
index 00000000..665aaaa7
--- /dev/null
+++ b/.gitlab/CODEOWNERS
@@ -0,0 +1,5 @@
+# Nix things
+.envrc @CobaltCause
+flake.lock @CobaltCause
+flake.nix @CobaltCause
+nix/ @CobaltCause
diff --git a/README.md b/README.md
index 730b2512..ab471769 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ Check out the [Conduit 1.0 Release Milestone](https://gitlab.com/famedly/conduit
 
 - Simple install (this was tested the most): [DEPLOY.md](DEPLOY.md)
 - Debian package: [debian/README.Debian](debian/README.Debian)
+- Nix/NixOS: [nix/README.md](nix/README.md)
 - Docker: [docker/README.md](docker/README.md)
 
 If you want to connect an Appservice to Conduit, take a look at [APPSERVICES.md](APPSERVICES.md).
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 00000000..9217ff26
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,102 @@
+{
+  "nodes": {
+    "fenix": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "rust-analyzer-src": "rust-analyzer-src"
+      },
+      "locked": {
+        "lastModified": 1665815894,
+        "narHash": "sha256-Vboo1L4NMGLKZKVLnOPi9OHlae7uoNyfgvyIUm+SVXE=",
+        "owner": "nix-community",
+        "repo": "fenix",
+        "rev": "2348450241a5f945f0ba07e44ecbfac2f541d7f4",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "fenix",
+        "type": "github"
+      }
+    },
+    "flake-utils": {
+      "locked": {
+        "lastModified": 1659877975,
+        "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "naersk": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1662220400,
+        "narHash": "sha256-9o2OGQqu4xyLZP9K6kNe1pTHnyPz0Wr3raGYnr9AIgY=",
+        "owner": "nix-community",
+        "repo": "naersk",
+        "rev": "6944160c19cb591eb85bbf9b2f2768a935623ed3",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "naersk",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1665856037,
+        "narHash": "sha256-/RvIWnGKdTSoIq5Xc2HwPIL0TzRslzU6Rqk4Img6UNg=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "c95ebc5125ffffcd431df0ad8620f0926b8125b8",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "fenix": "fenix",
+        "flake-utils": "flake-utils",
+        "naersk": "naersk",
+        "nixpkgs": "nixpkgs"
+      }
+    },
+    "rust-analyzer-src": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1665765556,
+        "narHash": "sha256-w9L5j0TIB5ay4aRwzGCp8mgvGsu5dVJQvbEFutwr6xE=",
+        "owner": "rust-lang",
+        "repo": "rust-analyzer",
+        "rev": "018b8429cf3fa9d8aed916704e41dfedeb0f4f78",
+        "type": "github"
+      },
+      "original": {
+        "owner": "rust-lang",
+        "ref": "nightly",
+        "repo": "rust-analyzer",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 00000000..924300cf
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,75 @@
+{
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs";
+    flake-utils.url = "github:numtide/flake-utils";
+
+    fenix = {
+      url = "github:nix-community/fenix";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    naersk = {
+      url = "github:nix-community/naersk";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+  };
+
+  outputs =
+    { self
+    , nixpkgs
+    , flake-utils
+
+    , fenix
+    , naersk
+    }: flake-utils.lib.eachDefaultSystem (system:
+    let
+      pkgs = nixpkgs.legacyPackages.${system};
+
+      # Nix-accessible `Cargo.toml`
+      cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
+
+      # The Rust toolchain to use
+      toolchain = fenix.packages.${system}.toolchainOf {
+        # Use the Rust version defined in `Cargo.toml`
+        channel = cargoToml.package.rust-version;
+
+        # This will need to be updated when `package.rust-version` is changed in
+        # `Cargo.toml`
+        sha256 = "sha256-KXx+ID0y4mg2B3LHp7IyaiMrdexF6octADnAtFIOjrY=";
+      };
+
+      builder = (pkgs.callPackage naersk {
+        inherit (toolchain) rustc cargo;
+      }).buildPackage;
+    in
+    {
+      packages.default = builder {
+        src = ./.;
+
+        nativeBuildInputs = (with pkgs.rustPlatform; [
+          bindgenHook
+        ]);
+      };
+
+      devShells.default = pkgs.mkShell {
+        # Rust Analyzer needs to be able to find the path to default crate
+        # sources, and it can read this environment variable to do so
+        RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library";
+
+        # Development tools
+        nativeBuildInputs = (with pkgs.rustPlatform; [
+          bindgenHook
+        ]) ++ (with toolchain; [
+          cargo
+          clippy
+          rust-src
+          rustc
+          rustfmt
+        ]);
+      };
+
+      checks = {
+        packagesDefault = self.packages.${system}.default;
+        devShellsDefault = self.devShells.${system}.default;
+      };
+    });
+}
diff --git a/nix/README.md b/nix/README.md
new file mode 100644
index 00000000..d92f910b
--- /dev/null
+++ b/nix/README.md
@@ -0,0 +1,188 @@
+# Conduit for Nix/NixOS
+
+This guide assumes you have a recent version of Nix (^2.4) installed.
+
+Since Conduit ships as a Nix flake, you'll first need to [enable
+flakes][enable_flakes].
+
+You can now use the usual Nix commands to interact with Conduit's flake. For
+example, `nix run gitlab:famedly/conduit` will run Conduit (though you'll need
+to provide configuration and such manually as usual).
+
+If your NixOS configuration is defined as a flake, you can depend on this flake
+to provide a more up-to-date version than provided by `nixpkgs`. In your flake,
+add the following to your `inputs`:
+
+```nix
+conduit = {
+    url = "gitlab:famedly/conduit";
+
+    # Assuming you have an input for nixpkgs called `nixpkgs`. If you experience
+    # build failures while using this, try commenting/deleting this line. This
+    # will probably also require you to always build from source.
+    inputs.nixpkgs.follows = "nixpkgs";
+};
+```
+
+Next, make sure you're passing your flake inputs to the `specialArgs` argument
+of `nixpkgs.lib.nixosSystem` [as explained here][specialargs]. This guide will
+assume you've named the group `flake-inputs`.
+
+Now you can configure Conduit and a reverse proxy for it. Add the following to
+a new Nix file and include it in your configuration:
+
+```nix
+{ config
+, pkgs
+, flake-inputs
+, ...
+}:
+
+let
+  # You'll need to edit these values
+
+  # The hostname that will appear in your user and room IDs
+  server_name = "example.com";
+
+  # The hostname that Conduit actually runs on
+  #
+  # This can be the same as `server_name` if you want. This is only necessary
+  # when Conduit is running on a different machine than the one hosting your
+  # root domain. This configuration also assumes this is all running on a single
+  # machine, some tweaks will need to be made if this is not the case.
+  matrix_hostname = "matrix.${server_name}";
+
+  # An admin email for TLS certificate notifications
+  admin_email = "admin@${server_name}";
+
+  # These ones you can leave alone
+
+  # Build a dervation that stores the content of `${server_name}/.well-known/matrix/server`
+  well_known_server = pkgs.writeText "well-known-matrix-server" ''
+    {
+      "m.server": "${matrix_hostname}"
+    }
+  '';
+
+  # Build a dervation that stores the content of `${server_name}/.well-known/matrix/client`
+  well_known_client = pkgs.writeText "well-known-matrix-client" ''
+    {
+      "m.homeserver": {
+        "base_url": "https://${matrix_hostname}"
+      }
+    }
+  '';
+in
+
+{
+  # Configure Conduit itself
+  services.matrix-conduit = {
+    enable = true;
+
+    # This causes NixOS to use the flake defined in this repository instead of
+    # the build of Conduit built into nixpkgs.
+    package = flake-inputs.conduit.packages.${pkgs.system}.default;
+
+    settings.global = {
+      inherit server_name;
+    };
+  };
+
+  # Configure automated TLS acquisition/renewal
+  security.acme = {
+    acceptTerms = true;
+    defaults = {
+      email = admin_email;
+    };
+  };
+
+  # ACME data must be readable by the NGINX user
+  users.users.nginx.extraGroups = [
+    "acme"
+  ];
+
+  # Configure NGINX as a reverse proxy
+  services.nginx = {
+    enable = true;
+    recommendedProxySettings = true;
+
+    virtualHosts = {
+      "${server_name}" = {
+        forceSSL = true;
+        enableACME = true;
+
+        listen = [
+          {
+            addr = "0.0.0.0";
+            port = 443;
+            ssl = true;
+          }
+          {
+            addr = "0.0.0.0";
+            port = 8448;
+            ssl = true;
+          }
+        ];
+
+        extraConfig = ''
+          merge_slashes off;
+        '';
+
+      "${matrix_hostname}" = {
+        forceSSL = true;
+        enableACME = true;
+
+        locations."/_matrix/" = {
+          proxyPass = "http://backend_conduit$request_uri";
+          proxyWebsockets = true;
+          extraConfig = ''
+            proxy_set_header Host $host;
+            proxy_buffering off;
+          '';
+        };
+
+        locations."=/.well-known/matrix/server" = {
+          # Use the contents of the derivation built previously
+          alias = "${well_known_server}";
+
+          extraConfig = ''
+            # Set the header since by default NGINX thinks it's just bytes
+            default_type application/json;
+          '';
+        };
+
+        locations."=/.well-known/matrix/client" = {
+          # Use the contents of the derivation built previously
+          alias = "${well_known_client}";
+
+          extraConfig = ''
+            # Set the header since by default NGINX thinks it's just bytes
+            default_type application/json;
+
+            # https://matrix.org/docs/spec/client_server/r0.4.0#web-browser-clients
+            add_header Access-Control-Allow-Origin "*";
+          '';
+        };
+      };
+    };
+
+    upstreams = {
+      "backend_conduit" = {
+        servers = {
+          "localhost:${toString config.services.matrix-conduit.settings.global.port}" = { };
+        };
+      };
+    };
+  };
+
+  # Open firewall ports for HTTP, HTTPS, and Matrix federation
+  networking.firewall.allowedTCPPorts = [ 80 443 8448 ];
+  networking.firewall.allowedUDPPorts = [ 80 443 8448 ];
+}
+```
+
+Now you can rebuild your system configuration and you should be good to go!
+
+[enable_flakes]: https://nixos.wiki/wiki/Flakes#Enable_flakes
+
+[specialargs]: https://nixos.wiki/wiki/Flakes#Using_nix_flakes_with_NixOS