diff --git a/include/utils/file.hpp b/include/utils/file.hpp
index 80436a8e..61a35137 100644
--- a/include/utils/file.hpp
+++ b/include/utils/file.hpp
@@ -106,6 +106,7 @@ namespace file_util {
   string contents(const string& filename);
   bool is_fifo(const string& filename);
   vector<string> glob(string pattern);
+  const string expand(const string& path);
 
   template <typename... Args>
   decltype(auto) make_file_descriptor(Args&&... args) {
diff --git a/src/components/config.cpp b/src/components/config.cpp
index f913d7ae..616eba1c 100644
--- a/src/components/config.cpp
+++ b/src/components/config.cpp
@@ -100,15 +100,16 @@ void config::parse_file() {
     }
 
     if (key == "include-file") {
-      if (value.empty() || !file_util::exists(value)) {
-        throw value_error("Invalid include file \"" + value + "\" defined on line " + to_string(lineno));
+      auto file_path = file_util::expand(value);
+      if (file_path.empty() || !file_util::exists(file_path)) {
+        throw value_error("Invalid include file \"" + file_path + "\" defined on line " + to_string(lineno));
       }
-      if (std::find(files.begin(), files.end(), value) != files.end()) {
-        throw value_error("Recursive include file \"" + value + "\"");
+      if (std::find(files.begin(), files.end(), file_path) != files.end()) {
+        throw value_error("Recursive include file \"" + file_path + "\"");
       }
-      files.push_back(value);
-      m_log.trace("config: Including file \"%s\"", value);
-      for (auto&& l : string_util::split(file_util::contents(value), '\n')) {
+      files.push_back(file_util::expand(file_path));
+      m_log.trace("config: Including file \"%s\"", file_path);
+      for (auto&& l : string_util::split(file_util::contents(file_path), '\n')) {
         pushline(lineno, forward<string>(l));
       }
       files.pop_back();
diff --git a/src/utils/file.cpp b/src/utils/file.cpp
index 5e6e58e0..d73c0fff 100644
--- a/src/utils/file.cpp
+++ b/src/utils/file.cpp
@@ -231,6 +231,17 @@ namespace file_util {
 
     return ret;
   }
+
+  /**
+   * Path expansion
+   */
+  const string expand(const string& path) {
+    string p{path};
+    if (p[0] == '~') {
+      p.replace(0, 1, env_util::get("HOME"));
+    }
+    return p;
+  }
 }
 
 POLYBAR_NS_END
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bad4595e..ce47a897 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -18,6 +18,7 @@ unit_test(utils/color)
 unit_test(utils/math)
 unit_test(utils/memory)
 unit_test(utils/string)
+unit_test(utils/file)
 unit_test(components/command_line)
 
 # XXX: Requires mocked xcb connection
diff --git a/tests/unit_tests/utils/file.cpp b/tests/unit_tests/utils/file.cpp
new file mode 100644
index 00000000..4aacd80d
--- /dev/null
+++ b/tests/unit_tests/utils/file.cpp
@@ -0,0 +1,21 @@
+#include <iomanip>
+#include <iostream>
+
+#include "components/logger.cpp"
+#include "utils/command.cpp"
+#include "utils/concurrency.cpp"
+#include "utils/env.cpp"
+#include "utils/file.cpp"
+#include "utils/io.cpp"
+#include "utils/process.cpp"
+#include "utils/string.cpp"
+
+int main() {
+  using namespace polybar;
+
+  "expand"_test = [] {
+    auto cmd = command_util::make_command("echo $HOME");
+    cmd->exec();
+    cmd->tail([](string home) { expect(file_util::expand("~/test") == home + "/test"); });
+  };
+}