{"id":348,"date":"2026-09-29T18:15:24","date_gmt":"2026-09-30T01:15:24","guid":{"rendered":"https:\/\/www.cmsws.com\/blog\/?p=348"},"modified":"2026-09-30T06:04:52","modified_gmt":"2026-09-30T13:04:52","slug":"self-hosting-actual-budget-on-debian-12-with-docker-and-apache","status":"publish","type":"post","link":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/","title":{"rendered":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Installing_Actual_Budget_on_Debian_12_with_Docker_Apache_and_Lets_Encrypt\"><\/span>Installing Actual Budget on Debian 12 with Docker, Apache, and Let&#8217;s Encrypt<span class=\"ez-toc-section-end\"><\/span><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let&#8217;s Encrypt SSL certificate.<\/p><div id=\"ez-toc-container\" class=\"ez-toc-v2_0_88 ez-toc-wrap-right counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<span class=\"ez-toc-title-toggle\"><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-1'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Installing_Actual_Budget_on_Debian_12_with_Docker_Apache_and_Lets_Encrypt\" >Installing Actual Budget on Debian 12 with Docker, Apache, and Let&#8217;s Encrypt<\/a><ul class='ez-toc-list-level-2' ><li class='ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#1_Update_Debian_12\" >1. Update Debian 12<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#2_Install_Docker_and_Docker_Compose\" >2. Install Docker and Docker Compose<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#3_Install_Apache\" >3. Install Apache<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#4_Install_Certbot\" >4. Install Certbot<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#5_Enable_the_Required_Apache_Modules\" >5. Enable the Required Apache Modules<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#A_Debian-specific_note_about_mod_headers\" >A Debian-specific note about mod_headers<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#6_Create_the_Actual_Budget_Directory\" >6. Create the Actual Budget Directory<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#7_Create_the_Docker_Compose_Configuration\" >7. Create the Docker Compose Configuration<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Why_Bind_Port_5006_to_127001\" >Why Bind Port 5006 to 127.0.0.1?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#8_Start_Actual_Budget\" >8. Start Actual Budget<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#9_Create_an_Apache_HTTP_Virtual_Host\" >9. Create an Apache HTTP Virtual Host<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Why_the_VirtualHost_IP_Matters\" >Why the VirtualHost IP Matters<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#10_Create_a_Temporary_Test_Page\" >10. Create a Temporary Test Page<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#11_Verify_Apaches_Virtual_Host_Selection\" >11. Verify Apache&#8217;s Virtual Host Selection<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#12_Obtain_the_Lets_Encrypt_Certificate\" >12. Obtain the Let&#8217;s Encrypt Certificate<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#13_Change_HTTP_to_an_HTTPS_Redirect\" >13. Change HTTP to an HTTPS Redirect<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#14_Configure_the_HTTPS_Reverse_Proxy\" >14. Configure the HTTPS Reverse Proxy<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#15_Understanding_the_Reverse_Proxy\" >15. Understanding the Reverse Proxy<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#16_Test_the_Apache_Configuration\" >16. Test the Apache Configuration<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#17_Test_Each_Layer_Separately\" >17. Test Each Layer Separately<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Test_Actual_Budget_Directly\" >Test Actual Budget Directly<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Test_HTTPS_Through_Apache\" >Test HTTPS Through Apache<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Test_the_HTTP_Redirect\" >Test the HTTP Redirect<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#18_Useful_Docker_Commands\" >18. Useful Docker Commands<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-26\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#19_Useful_Apache_Commands\" >19. Useful Apache Commands<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-27\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#20_Useful_Certbot_Commands\" >20. Useful Certbot Commands<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-28\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#21_Updating_Actual_Budget\" >21. Updating Actual Budget<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-29\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#22_Final_Configuration\" >22. Final Configuration<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-30\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#23_Common_Problems\" >23. Common Problems<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-31\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Apache_Displays_Another_Hosted_Website\" >Apache Displays Another Hosted Website<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-32\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Apache_Reports_%E2%80%9CInvalid_command_%E2%80%98RequestHeader%E2%80%9D\" >Apache Reports &#8220;Invalid command &#8216;RequestHeader&#8217;&#8221;<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-33\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#a2enmod_Says_mod_headers_Does_Not_Exist\" >a2enmod Says mod_headers Does Not Exist<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-34\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Apache_Returns_502_Bad_Gateway\" >Apache Returns 502 Bad Gateway<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-35\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Certbot_Cannot_Validate_the_Domain\" >Certbot Cannot Validate the Domain<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-36\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n\n\n\n\n<p class=\"wp-block-paragraph\">The resulting configuration looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Internet\n   |\n   +-- HTTP :80 --&gt; Apache --&gt; HTTPS redirect\n   |\n   +-- HTTPS :443\n          |\n          v\n      Apache 2.4\n      Let's Encrypt TLS\n          |\n          | HTTP over localhost\n          v\n     127.0.0.1:5006\n          |\n          v\n   Actual Budget Docker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Docker container is deliberately bound only to <code>127.0.0.1<\/code>. This prevents users on the Internet from connecting directly to Actual Budget on port 5006 and bypassing Apache.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the examples below, replace:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>actual.example.com<\/code> with the hostname you intend to use.<\/li>\n\n\n\n<li><code>SERVER_IP<\/code> with the IP address on which Apache is listening.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>actual.example.com\nSERVER_IP<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">might become:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>actual.yourdomain.com\n192.0.2.10<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your DNS record for <code>actual.example.com<\/code> must point to the public IP address of the server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"1_Update_Debian_12\"><\/span>1. Update Debian 12<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start by updating the system:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\nsudo apt upgrade -y<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"2_Install_Docker_and_Docker_Compose\"><\/span>2. Install Docker and Docker Compose<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Install Docker:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y docker.io<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install Docker Compose support appropriate for your Debian 12 installation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the Docker Compose plugin is available:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y docker-compose-plugin<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can check whether Compose is available with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If your Debian repository configuration provides the older standalone Compose package instead, it can be installed with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y docker-compose<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This guide uses the modern command syntax:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enable Docker at boot and start it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl enable --now docker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify that Docker is running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status docker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can also verify the installation with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker --version\ndocker compose version<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"3_Install_Apache\"><\/span>3. Install Apache<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Install Apache:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enable Apache at boot and start it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl enable --now apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify its status:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can verify that Apache is listening on port 80 with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ss -lntp | grep ':80'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"4_Install_Certbot\"><\/span>4. Install Certbot<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Install Certbot and its Apache integration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y certbot python3-certbot-apache<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify the installation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>certbot --version<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"5_Enable_the_Required_Apache_Modules\"><\/span>5. Enable the Required Apache Modules<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Enable the Apache modules required for the reverse proxy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo a2enmod proxy\nsudo a2enmod proxy_http\nsudo a2enmod proxy_wstunnel\nsudo a2enmod headers\nsudo a2enmod rewrite\nsudo a2enmod ssl<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These modules provide the following functionality:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>proxy<\/code> \u2014 Apache&#8217;s core proxy functionality.<\/li>\n\n\n\n<li><code>proxy_http<\/code> \u2014 Allows Apache to proxy HTTP connections.<\/li>\n\n\n\n<li><code>proxy_wstunnel<\/code> \u2014 Provides WebSocket proxy support.<\/li>\n\n\n\n<li><code>headers<\/code> \u2014 Allows Apache to manipulate HTTP headers.<\/li>\n\n\n\n<li><code>rewrite<\/code> \u2014 Provides URL rewriting support if needed.<\/li>\n\n\n\n<li><code>ssl<\/code> \u2014 Enables HTTPS\/TLS support.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"A_Debian-specific_note_about_mod_headers\"><\/span>A Debian-specific note about mod_headers<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The Apache module is commonly referred to as <code>mod_headers<\/code>, but Debian&#8217;s <code>a2enmod<\/code> utility expects:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo a2enmod headers<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can verify the loaded modules with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>apache2ctl -M | grep -E 'proxy|headers|rewrite|ssl'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test the Apache configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl configtest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If everything is correct, Apache should report:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Syntax OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then restart Apache:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl restart apache2<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"6_Create_the_Actual_Budget_Directory\"><\/span>6. Create the Actual Budget Directory<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create a directory for Actual Budget and its persistent data:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir -p \/opt\/actual\/data\ncd \/opt\/actual<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The resulting structure will eventually look similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/opt\/actual\/\n\u251c\u2500\u2500 docker-compose.yml\n\u2514\u2500\u2500 data\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>data<\/code> directory is important because it allows Actual Budget&#8217;s persistent data to survive container replacement or upgrades.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"7_Create_the_Docker_Compose_Configuration\"><\/span>7. Create the Docker Compose Configuration<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create the Compose file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nano \/opt\/actual\/docker-compose.yml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>services:\n  actual:\n    image: actualbudget\/actual-server:latest\n    container_name: actual_server\n    restart: unless-stopped\n    ports:\n      - \"127.0.0.1:5006:5006\"\n    volumes:\n      - .\/data:\/data<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Save the file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Why_Bind_Port_5006_to_127001\"><\/span>Why Bind Port 5006 to 127.0.0.1?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You may see examples using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ports:\n  - \"5006:5006\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That can expose port 5006 on every network interface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because Apache is going to be the public-facing server, there is no reason for clients on the Internet to communicate directly with the Actual Budget container.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead, use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ports:\n  - \"127.0.0.1:5006:5006\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The resulting path is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Internet\n    |\n    v\nApache\n    |\n    v\n127.0.0.1:5006\n    |\n    v\nActual Budget<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Only applications running locally on the Debian server can connect directly to port 5006.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"8_Start_Actual_Budget\"><\/span>8. Start Actual Budget<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start the container:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/opt\/actual\nsudo docker compose up -d<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify that it is running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can also use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Before configuring Apache as a reverse proxy, verify that Actual Budget itself works:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -v http:\/\/127.0.0.1:5006\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should receive an HTTP response from Actual Budget.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can also verify the listening socket:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ss -lntp | grep 5006<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ideally, port 5006 should be associated with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>127.0.0.1:5006<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">rather than:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>0.0.0.0:5006<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If <code>curl http:\/\/127.0.0.1:5006\/<\/code> does not work, fix the Docker or Actual Budget problem before troubleshooting Apache.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"9_Create_an_Apache_HTTP_Virtual_Host\"><\/span>9. Create an Apache HTTP Virtual Host<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nano \/etc\/apache2\/sites-available\/actual-budget.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For initial testing, use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost SERVER_IP:80&gt;\n\n    ServerName actual.example.com\n\n    DocumentRoot \/var\/www\/actual-test\n\n    &lt;Directory \/var\/www\/actual-test&gt;\n        Options FollowSymLinks\n        AllowOverride None\n        Require all granted\n    &lt;\/Directory&gt;\n\n    ErrorLog ${APACHE_LOG_DIR}\/actual_error.log\n    CustomLog ${APACHE_LOG_DIR}\/actual_access.log combined\n\n&lt;\/VirtualHost&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Remember to replace <code>SERVER_IP<\/code> with the address Apache uses for its other virtual hosts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost 192.0.2.10:80&gt;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Why_the_VirtualHost_IP_Matters\"><\/span>Why the VirtualHost IP Matters<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This is particularly important on Apache servers hosting multiple domains.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your existing virtual hosts use explicit addresses such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost 192.0.2.10:80&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">do not assume that creating the new site as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost *:80&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">will behave identically.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the address\/port binding strategy consistent with the server&#8217;s existing virtual hosts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A symptom of an incorrect vhost match is requesting:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>http:&#47;&#47;actual.example.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and receiving one of the server&#8217;s other websites instead.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When Apache cannot find the expected hostname within the applicable virtual-host set, another vhost may become the default for that IP address and port.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl -S<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is extremely useful for diagnosing this situation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"10_Create_a_Temporary_Test_Page\"><\/span>10. Create a Temporary Test Page<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create the temporary document root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir -p \/var\/www\/actual-test<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create a test page:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo '&lt;h1&gt;Apache is working for actual.example.com&lt;\/h1&gt;' | \\\n    sudo tee \/var\/www\/actual-test\/index.html<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enable the site:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo a2ensite actual-budget.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test the Apache configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl configtest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you receive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Syntax OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">reload Apache:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl reload apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now visit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>http:&#47;&#47;actual.example.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Apache is working for actual.example.com<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"11_Verify_Apaches_Virtual_Host_Selection\"><\/span>11. Verify Apache&#8217;s Virtual Host Selection<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before proceeding with SSL, check exactly how Apache sees the configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl -S<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Look for <code>actual.example.com<\/code> under the appropriate IP address and port 80.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the browser displays a different website hosted on the same server, <code>apache2ctl -S<\/code> should be one of the first diagnostic commands you run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can also test the virtual host locally without relying on external DNS:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -H \"Host: actual.example.com\" http:\/\/127.0.0.1\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If Apache listens only on a specific local address rather than loopback, test that address instead.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"12_Obtain_the_Lets_Encrypt_Certificate\"><\/span>12. Obtain the Let&#8217;s Encrypt Certificate<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once the HTTP virtual host works correctly and public DNS for <code>actual.example.com<\/code> points to the server, request the certificate:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot --apache -d actual.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Certbot should obtain the certificate and configure Apache&#8217;s SSL support.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">After Certbot finishes, run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl configtest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should receive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Syntax OK<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"13_Change_HTTP_to_an_HTTPS_Redirect\"><\/span>13. Change HTTP to an HTTPS Redirect<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once HTTPS is operational, the temporary test page is no longer necessary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Change <code>\/etc\/apache2\/sites-available\/actual-budget.conf<\/code> to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost SERVER_IP:80&gt;\n\n    ServerName actual.example.com\n\n    ErrorLog ${APACHE_LOG_DIR}\/actual_error.log\n    CustomLog ${APACHE_LOG_DIR}\/actual_access.log combined\n\n    Redirect permanent \/ https:\/\/actual.example.com\/\n\n&lt;\/VirtualHost&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Again, replace <code>SERVER_IP<\/code> with the appropriate server address.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Requests to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>http:&#47;&#47;actual.example.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">will now be redirected to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;actual.example.com\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"14_Configure_the_HTTPS_Reverse_Proxy\"><\/span>14. Configure the HTTPS Reverse Proxy<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Certbot will normally create or modify an SSL virtual-host configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Depending on the Certbot version and existing Apache configuration, you may see a file such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/etc\/apache2\/sites-available\/actual-budget-le-ssl.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The HTTPS virtual host should contain the equivalent of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;IfModule mod_ssl.c&gt;\n&lt;VirtualHost SERVER_IP:443&gt;\n\n    ServerName actual.example.com\n\n    ErrorLog ${APACHE_LOG_DIR}\/actual_error.log\n    CustomLog ${APACHE_LOG_DIR}\/actual_access.log combined\n\n    ProxyRequests Off\n    ProxyPreserveHost On\n\n    ProxyPass        \/ http:\/\/127.0.0.1:5006\/\n    ProxyPassReverse \/ http:\/\/127.0.0.1:5006\/\n\n    RequestHeader set X-Forwarded-Proto \"https\"\n\n    SSLCertificateFile \/etc\/letsencrypt\/live\/actual.example.com\/fullchain.pem\n    SSLCertificateKeyFile \/etc\/letsencrypt\/live\/actual.example.com\/privkey.pem\n\n    Include \/etc\/letsencrypt\/options-ssl-apache.conf\n\n&lt;\/VirtualHost&gt;\n&lt;\/IfModule&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Do not blindly replace certificate paths that Certbot has generated. If your Certbot-created configuration differs, retain the certificate paths it supplied.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remove the temporary <code>DocumentRoot<\/code> and <code>&lt;Directory&gt;<\/code> configuration from the HTTPS vhost if Certbot copied them into it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"15_Understanding_the_Reverse_Proxy\"><\/span>15. Understanding the Reverse Proxy<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The completed request path is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Browser\n   |\n   | HTTPS :443\n   v\nApache\n   |\n   | HTTP\n   v\n127.0.0.1:5006\n   |\n   v\nActual Budget<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Apache handles the public TLS connection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Actual Budget receives an ordinary HTTP connection from Apache over the server&#8217;s loopback interface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This directive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ProxyPreserveHost On<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">preserves the original HTTP <code>Host<\/code> header.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This directive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>RequestHeader set X-Forwarded-Proto \"https\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">informs the backend that the original client connection used HTTPS even though Apache&#8217;s connection to the backend uses HTTP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The latter directive requires the Apache <code>headers<\/code> module:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo a2enmod headers<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"16_Test_the_Apache_Configuration\"><\/span>16. Test the Apache Configuration<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Always test Apache before restarting or reloading it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl configtest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The expected result is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Syntax OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then reload:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl reload apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You can inspect all configured virtual hosts with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl -S<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify that <code>actual.example.com<\/code> appears on both port 80 and port 443 using the expected server IP address.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"17_Test_Each_Layer_Separately\"><\/span>17. Test Each Layer Separately<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When troubleshooting a reverse proxy, testing each layer separately can save considerable time.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Test_Actual_Budget_Directly\"><\/span>Test Actual Budget Directly<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -v http:\/\/127.0.0.1:5006\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If this fails, the problem is probably Docker or Actual Budget rather than Apache.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Test_HTTPS_Through_Apache\"><\/span>Test HTTPS Through Apache<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -vk https:\/\/actual.example.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the direct localhost test works but this fails, investigate Apache, the SSL configuration, or the reverse-proxy configuration.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Test_the_HTTP_Redirect\"><\/span>Test the HTTP Redirect<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -v http:\/\/actual.example.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should receive a response similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/1.1 301 Moved Permanently\nLocation: https:\/\/actual.example.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A useful troubleshooting sequence is therefore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Does http:\/\/127.0.0.1:5006\/ work?\n        |\n        +-- NO --&gt; Docker \/ Actual Budget problem\n        |\n        +-- YES\n             |\n             v\nDoes https:\/\/actual.example.com\/ work?\n        |\n        +-- NO --&gt; Apache proxy \/ SSL problem\n        |\n        +-- YES\n             |\n             v\nDoes http:\/\/actual.example.com\/ redirect?\n        |\n        +-- NO --&gt; Port 80 virtual-host problem\n        |\n        +-- YES --&gt; Configuration operational<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"18_Useful_Docker_Commands\"><\/span>18. Useful Docker Commands<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Check running containers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check the Actual Budget container:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/opt\/actual\nsudo docker compose ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">View its logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose logs<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Follow logs continuously:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose logs -f<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Restart Actual Budget:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose restart<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Stop the application:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose down<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Start it again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose up -d<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"19_Useful_Apache_Commands\"><\/span>19. Useful Apache Commands<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Check Apache&#8217;s configuration syntax:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl configtest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Display Apache&#8217;s virtual-host mapping:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl -S<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Display loaded modules:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl -M<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check service status:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reload configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl reload apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Restart Apache:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl restart apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">View systemd logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo journalctl -u apache2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Follow the Actual Budget Apache logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tail -f \/var\/log\/apache2\/actual_access.log<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tail -f \/var\/log\/apache2\/actual_error.log<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"20_Useful_Certbot_Commands\"><\/span>20. Useful Certbot Commands<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">List installed certificates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot certificates<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test automatic certificate renewal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot renew --dry-run<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On Debian 12, you can inspect Certbot&#8217;s renewal timer with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl status certbot.timer<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"21_Updating_Actual_Budget\"><\/span>21. Updating Actual Budget<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Because Actual Budget is running in Docker, updating it is straightforward.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Change to the application directory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/opt\/actual<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Download the current image:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose pull<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Recreate the container using the new image:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose up -d<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check its status:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then inspect the logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose logs --tail=100<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because the Actual Budget data resides in:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/opt\/actual\/data<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">rather than solely inside the container, recreating the container does not normally remove the persistent application data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Backing up this directory before significant upgrades is nevertheless strongly recommended.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"22_Final_Configuration\"><\/span>22. Final Configuration<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The completed Debian 12 installation consists of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Debian 12\n   |\n   +-- Docker\n   |     |\n   |     +-- Actual Budget\n   |           |\n   |           +-- 127.0.0.1:5006\n   |\n   +-- Apache 2.4\n         |\n         +-- actual.example.com:80\n         |      |\n         |      +-- Redirect to HTTPS\n         |\n         +-- actual.example.com:443\n                |\n                +-- Let's Encrypt TLS\n                |\n                +-- Reverse proxy\n                       |\n                       +-- http:\/\/127.0.0.1:5006\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The important characteristics of this configuration are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Actual Budget runs independently inside Docker.<\/li>\n\n\n\n<li>Persistent data is stored outside the container.<\/li>\n\n\n\n<li>Port 5006 is bound only to <code>127.0.0.1<\/code>.<\/li>\n\n\n\n<li>Apache is the only public-facing web service.<\/li>\n\n\n\n<li>HTTP requests are redirected to HTTPS.<\/li>\n\n\n\n<li>Apache terminates the TLS connection.<\/li>\n\n\n\n<li>Let&#8217;s Encrypt provides the SSL certificate.<\/li>\n\n\n\n<li>Apache proxies requests internally to Actual Budget.<\/li>\n\n\n\n<li>The Docker service automatically restarts unless explicitly stopped.<\/li>\n\n\n\n<li>Apache and Docker are configured to start automatically with Debian.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"23_Common_Problems\"><\/span>23. Common Problems<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Apache_Displays_Another_Hosted_Website\"><\/span>Apache Displays Another Hosted Website<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl -S<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check whether <code>actual.example.com<\/code> is associated with the correct IP address and port.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your existing sites use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost SERVER_IP:80&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost SERVER_IP:443&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">use the same binding convention for Actual Budget rather than mixing those virtual hosts with <code>*:80<\/code> and <code>*:443<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Apache Reports &#8220;Invalid command &#8216;RequestHeader'&#8221;<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl configtest<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">reports:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Invalid command 'RequestHeader', perhaps misspelled or defined by a module not included in the server configuration<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">enable the headers module:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo a2enmod headers<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then test again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apache2ctl configtest<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"a2enmod_Says_mod_headers_Does_Not_Exist\"><\/span>a2enmod Says mod_headers Does Not Exist<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Do not run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo a2enmod mod_headers<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On Debian, use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo a2enmod headers<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Apache_Returns_502_Bad_Gateway\"><\/span>Apache Returns 502 Bad Gateway<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">First test Actual Budget directly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -v http:\/\/127.0.0.1:5006\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker compose logs<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the backend isn&#8217;t responding on <code>127.0.0.1:5006<\/code>, Apache cannot proxy requests to it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Certbot_Cannot_Validate_the_Domain\"><\/span>Certbot Cannot Validate the Domain<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before running Certbot, verify that:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>http:&#47;&#47;actual.example.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">reaches the correct Apache virtual host from the Internet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also verify that the domain&#8217;s DNS record points to the server and that inbound TCP port 80 is reachable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Running Actual Budget behind Apache provides a clean way to integrate the application into an existing Debian 12 web server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The most important design decision is keeping Actual Budget&#8217;s Docker port private:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>127.0.0.1:5006<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">rather than exposing port 5006 publicly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apache remains responsible for public HTTP and HTTPS connections, Let&#8217;s Encrypt provides TLS certificates, and Actual Budget remains isolated behind the reverse proxy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This arrangement works particularly well on servers that already host multiple Apache virtual hosts because Actual Budget can be added as another hostname without requiring an additional public web port.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Installing Actual Budget on Debian 12 with Docker, Apache, and Let&#8217;s Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let&#8217;s Encrypt SSL certificate. The resulting configuration looks [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[40,25,44],"tags":[19,15],"class_list":["post-348","post","type-post","status-publish","format-standard","hentry","category-cloud-computing","category-linux","category-network-services","tag-apache-web-server","tag-debian"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"Installing Actual Budget on Debian 12 with Docker, Apache, and Let&#039;s Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let&#039;s Encrypt SSL\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Jim Lucas\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"A different perspective \u203a Right, lets get on with it...\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \u203a A different perspective\" \/>\n\t\t<meta property=\"og:description\" content=\"Installing Actual Budget on Debian 12 with Docker, Apache, and Let&#039;s Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let&#039;s Encrypt SSL\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-09-30T01:15:24+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-09-30T13:04:52+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \u203a A different perspective\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Installing Actual Budget on Debian 12 with Docker, Apache, and Let&#039;s Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let&#039;s Encrypt SSL\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#blogposting\",\"name\":\"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \\u203a A different perspective\",\"headline\":\"Self-Hosting Actual Budget on Debian 12 with Docker and Apache\",\"author\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/author\\\/jlucas\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/#organization\"},\"datePublished\":\"2026-09-29T18:15:24-07:00\",\"dateModified\":\"2026-09-30T06:04:52-07:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#webpage\"},\"articleSection\":\"Cloud Computing, Linux, Network Services, Apache Web Server, Debian\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/category\\\/linux\\\/#listItem\",\"name\":\"Linux\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/category\\\/linux\\\/#listItem\",\"position\":2,\"name\":\"Linux\",\"item\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/category\\\/linux\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#listItem\",\"name\":\"Self-Hosting Actual Budget on Debian 12 with Docker and Apache\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#listItem\",\"position\":3,\"name\":\"Self-Hosting Actual Budget on Debian 12 with Docker and Apache\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/category\\\/linux\\\/#listItem\",\"name\":\"Linux\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/#organization\",\"name\":\"A different perspective\",\"description\":\"Right, lets get on with it...\",\"url\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/\",\"telephone\":\"+15414085189\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/author\\\/jlucas\\\/#author\",\"url\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/author\\\/jlucas\\\/\",\"name\":\"Jim Lucas\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/3cbbbf6cb05c36455e4560d55d14ec27e194a9639eef5b88d16f0d547e8c55c7?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Jim Lucas\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#webpage\",\"url\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/\",\"name\":\"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \\u203a A different perspective\",\"description\":\"Installing Actual Budget on Debian 12 with Docker, Apache, and Let's Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let's Encrypt SSL\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/author\\\/jlucas\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/author\\\/jlucas\\\/#author\"},\"datePublished\":\"2026-09-29T18:15:24-07:00\",\"dateModified\":\"2026-09-30T06:04:52-07:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/\",\"name\":\"A different perspective\",\"description\":\"Right, lets get on with it...\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.cmsws.com\\\/blog\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \u203a A different perspective","description":"Installing Actual Budget on Debian 12 with Docker, Apache, and Let's Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let's Encrypt SSL","canonical_url":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#blogposting","name":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \u203a A different perspective","headline":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache","author":{"@id":"https:\/\/www.cmsws.com\/blog\/author\/jlucas\/#author"},"publisher":{"@id":"https:\/\/www.cmsws.com\/blog\/#organization"},"datePublished":"2026-09-29T18:15:24-07:00","dateModified":"2026-09-30T06:04:52-07:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#webpage"},"isPartOf":{"@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#webpage"},"articleSection":"Cloud Computing, Linux, Network Services, Apache Web Server, Debian"},{"@type":"BreadcrumbList","@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/www.cmsws.com\/blog\/#listItem","position":1,"name":"Home","item":"https:\/\/www.cmsws.com\/blog\/","nextItem":{"@type":"ListItem","@id":"https:\/\/www.cmsws.com\/blog\/category\/linux\/#listItem","name":"Linux"}},{"@type":"ListItem","@id":"https:\/\/www.cmsws.com\/blog\/category\/linux\/#listItem","position":2,"name":"Linux","item":"https:\/\/www.cmsws.com\/blog\/category\/linux\/","nextItem":{"@type":"ListItem","@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#listItem","name":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache"},"previousItem":{"@type":"ListItem","@id":"https:\/\/www.cmsws.com\/blog\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#listItem","position":3,"name":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache","previousItem":{"@type":"ListItem","@id":"https:\/\/www.cmsws.com\/blog\/category\/linux\/#listItem","name":"Linux"}}]},{"@type":"Organization","@id":"https:\/\/www.cmsws.com\/blog\/#organization","name":"A different perspective","description":"Right, lets get on with it...","url":"https:\/\/www.cmsws.com\/blog\/","telephone":"+15414085189"},{"@type":"Person","@id":"https:\/\/www.cmsws.com\/blog\/author\/jlucas\/#author","url":"https:\/\/www.cmsws.com\/blog\/author\/jlucas\/","name":"Jim Lucas","image":{"@type":"ImageObject","@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/3cbbbf6cb05c36455e4560d55d14ec27e194a9639eef5b88d16f0d547e8c55c7?s=96&d=mm&r=g","width":96,"height":96,"caption":"Jim Lucas"}},{"@type":"WebPage","@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#webpage","url":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/","name":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \u203a A different perspective","description":"Installing Actual Budget on Debian 12 with Docker, Apache, and Let's Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let's Encrypt SSL","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/www.cmsws.com\/blog\/#website"},"breadcrumb":{"@id":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/#breadcrumblist"},"author":{"@id":"https:\/\/www.cmsws.com\/blog\/author\/jlucas\/#author"},"creator":{"@id":"https:\/\/www.cmsws.com\/blog\/author\/jlucas\/#author"},"datePublished":"2026-09-29T18:15:24-07:00","dateModified":"2026-09-30T06:04:52-07:00"},{"@type":"WebSite","@id":"https:\/\/www.cmsws.com\/blog\/#website","url":"https:\/\/www.cmsws.com\/blog\/","name":"A different perspective","description":"Right, lets get on with it...","inLanguage":"en-US","publisher":{"@id":"https:\/\/www.cmsws.com\/blog\/#organization"}}]},"og:locale":"en_US","og:site_name":"A different perspective \u203a Right, lets get on with it...","og:type":"article","og:title":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \u203a A different perspective","og:description":"Installing Actual Budget on Debian 12 with Docker, Apache, and Let's Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let's Encrypt SSL","og:url":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/","article:published_time":"2026-09-30T01:15:24+00:00","article:modified_time":"2026-09-30T13:04:52+00:00","twitter:card":"summary_large_image","twitter:title":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache \u203a A different perspective","twitter:description":"Installing Actual Budget on Debian 12 with Docker, Apache, and Let's Encrypt Actual Budget is a privacy-focused personal finance application that can be self-hosted. This guide explains how to install Actual Budget on a Debian 12 server using Docker, place it behind an existing Apache web server, and secure it with a Let's Encrypt SSL"},"aioseo_meta_data":{"post_id":"348","title":null,"description":null,"keywords":null,"keyphrases":{"focus":{"keyphrase":"","score":0,"analysis":{"keyphraseInTitle":{"score":0,"maxScore":9,"error":1}}},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":null,"frequency":"default","local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-09-30 01:15:27","updated":"2026-09-30 13:05:03","seo_analyzer_scan_date":null,"focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.cmsws.com\/blog\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.cmsws.com\/blog\/category\/linux\/\" title=\"Linux\">Linux<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tSelf-Hosting Actual Budget on Debian 12 with Docker and Apache\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/www.cmsws.com\/blog\/"},{"label":"Linux","link":"https:\/\/www.cmsws.com\/blog\/category\/linux\/"},{"label":"Self-Hosting Actual Budget on Debian 12 with Docker and Apache","link":"https:\/\/www.cmsws.com\/blog\/self-hosting-actual-budget-on-debian-12-with-docker-and-apache\/"}],"_links":{"self":[{"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/posts\/348","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/comments?post=348"}],"version-history":[{"count":2,"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/posts\/348\/revisions"}],"predecessor-version":[{"id":351,"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/posts\/348\/revisions\/351"}],"wp:attachment":[{"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/media?parent=348"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/categories?post=348"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cmsws.com\/blog\/wp-json\/wp\/v2\/tags?post=348"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}