diff --git a/scripts/clmc-service/install.sh b/scripts/clmc-service/install.sh
index 8f1132b6dd47550da9ef2ba5d79aae09c8d69174..cee52f13204bc2e0ceede51118c0273d3323223b 100755
--- a/scripts/clmc-service/install.sh
+++ b/scripts/clmc-service/install.sh
@@ -162,6 +162,10 @@ fi
 echo "----> Creating CLMC web service log directory"
 mkdir -p /var/log/flame/clmc
 
+# create directory for CLMC service config
+echo "----> Creating CLMC web service config directory"
+mkdir -p /etc/flame/clmc
+
 # Install minioclmc as systemctl service
 # -----------------------------------------------------------------------
 mkdir -p /opt/flame/clmc
diff --git a/src/service/clmcservice/__init__.py b/src/service/clmcservice/__init__.py
index 3473f9c9e91d777883c4a01d8a703f99643b677e..a3e6c20ce747d356dab68e4b8352f47c9e5ae41b 100644
--- a/src/service/clmcservice/__init__.py
+++ b/src/service/clmcservice/__init__.py
@@ -1,3 +1,4 @@
+#!/usr/bin/python3
 """
 // © University of Southampton IT Innovation Centre, 2018
 //
@@ -22,7 +23,7 @@
 """
 
 from pyramid.config import Configurator
-from clmcservice.utilities import RUNNING_FLAG, MALFORMED_FLAG
+from clmcservice.utilities import validate_conf_file, RUNNING_FLAG, MALFORMED_FLAG, CONF_FILE_ATTRIBUTE, CONF_OBJECT, AGGREGATOR_CONFIG_SECTION
 
 
 def main(global_config, **settings):
@@ -30,9 +31,10 @@ def main(global_config, **settings):
     This function returns a Pyramid WSGI application.
     """
 
-    # a conversion is necessary so that the configuration values of the aggregator are stored with the right type instead of strings
-    aggregator_report_period = int(settings.get('aggregator_report_period', 5))
-    settings['aggregator_report_period'] = aggregator_report_period
+    # validate and use (if valid) the configuration file
+    conf_file_path = settings[CONF_FILE_ATTRIBUTE]
+    conf = validate_conf_file(conf_file_path)  # if None returned here, service is in unconfigured state
+    settings[CONF_OBJECT] = conf
 
     settings[MALFORMED_FLAG] = False
 
diff --git a/src/service/clmcservice/tests.py b/src/service/clmcservice/tests.py
index d9980cb7ff8767e61842269d24763a287fbe255e..df9fccbaab53617bb44f51407f7b04343e0bd4c9 100644
--- a/src/service/clmcservice/tests.py
+++ b/src/service/clmcservice/tests.py
@@ -1,3 +1,4 @@
+#!/usr/bin/python3
 """
 // © University of Southampton IT Innovation Centre, 2018
 //
@@ -21,13 +22,14 @@
 //      Created for Project :   FLAME
 """
 
-import pytest
 from pyramid import testing
 from pyramid.httpexceptions import HTTPBadRequest
 from time import sleep
-from clmcservice.utilities import CONFIG_ATTRIBUTES, PROCESS_ATTRIBUTE, RUNNING_FLAG, MALFORMED_FLAG, URL_REGEX
+from clmcservice.utilities import CONF_FILE_ATTRIBUTE, CONF_OBJECT, AGGREGATOR_CONFIG_SECTION, CONFIG_ATTRIBUTES, PROCESS_ATTRIBUTE, RUNNING_FLAG, MALFORMED_FLAG, URL_REGEX
+import pytest
 import os
 import signal
+import configparser
 
 
 class TestAggregatorAPI(object):
@@ -41,9 +43,10 @@ class TestAggregatorAPI(object):
         A fixture to implement setUp/tearDown functionality for all tests by initializing configuration structure for the web service
         """
 
-        self.config = testing.setUp()
-        self.config.add_settings({'aggregator_running': False, 'malformed': False, 'aggregator_report_period': 5,
-                                  'aggregator_database_name': 'CLMCMetrics', 'aggregator_database_url': "http://172.40.231.51:8086"})
+        self.registry = testing.setUp()
+        config = configparser.ConfigParser()
+        config[AGGREGATOR_CONFIG_SECTION] = {'aggregator_report_period': 5, 'aggregator_database_name': 'CLMCMetrics', 'aggregator_database_url': "http://172.40.231.51:8086"}
+        self.registry.add_settings({'configuration_object': config, 'aggregator_running': False, 'malformed': False, 'configuration_file_path': "/etc/flame/clmc/service.conf"})
 
         yield
 
@@ -56,9 +59,9 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorConfig  # nested import so that importing the class view is part of the test itself
 
-        assert self.config.get_settings().get('aggregator_report_period') == 5, "Initial report period is 5 seconds."
-        assert self.config.get_settings().get('aggregator_database_name') == 'CLMCMetrics', "Initial database name the aggregator uses is CLMCMetrics."
-        assert self.config.get_settings().get('aggregator_database_url') == "http://172.40.231.51:8086", "Initial aggregator url is http://172.40.231.51:8086"
+        assert int(self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_report_period')) == 5, "Initial report period is 5 seconds."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_name') == 'CLMCMetrics', "Initial database name the aggregator uses is CLMCMetrics."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_url') == "http://172.40.231.51:8086", "Initial aggregator url is http://172.40.231.51:8086"
 
         request = testing.DummyRequest()
         response = AggregatorConfig(request).get()
@@ -67,9 +70,9 @@ class TestAggregatorAPI(object):
                             'aggregator_database_name': 'CLMCMetrics',
                             'aggregator_database_url': "http://172.40.231.51:8086"}, "Response must be a dictionary representing a JSON object with the correct configuration data of the aggregator."
 
-        assert self.config.get_settings().get('aggregator_report_period') == 5, "A GET request must not modify the aggregator configuration data."
-        assert self.config.get_settings().get('aggregator_database_name') == 'CLMCMetrics', "A GET request must not modify the aggregator configuration data."
-        assert self.config.get_settings().get('aggregator_database_url') == "http://172.40.231.51:8086", "A GET request must not modify the aggregator configuration data."
+        assert int(self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_report_period')) == 5, "A GET request must not modify the aggregator configuration data."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_name') == 'CLMCMetrics', "A GET request must not modify the aggregator configuration data."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_url') == "http://172.40.231.51:8086", "A GET request must not modify the aggregator configuration data."
 
     @pytest.mark.parametrize("input_body, output_value", [
         ('{"aggregator_report_period": 10, "aggregator_database_name": "CLMCMetrics", "aggregator_database_url": "http://171.40.231.51:8086"}',
@@ -101,10 +104,10 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorConfig, AggregatorController  # nested import so that importing the class view is part of the test itself
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
-        assert self.config.get_settings().get('aggregator_report_period') == 5, "Initial report period is 5 seconds."
-        assert self.config.get_settings().get('aggregator_database_name') == 'CLMCMetrics', "Initial database name the aggregator uses is CLMCMetrics."
-        assert self.config.get_settings().get('aggregator_database_url') == "http://172.40.231.51:8086", "Initial aggregator url is http://172.40.231.51:8086"
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
+        assert int(self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_report_period')) == 5, "Initial report period is 5 seconds."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_name') == 'CLMCMetrics', "Initial database name the aggregator uses is CLMCMetrics."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_url') == "http://172.40.231.51:8086", "Initial aggregator url is http://172.40.231.51:8086"
 
         request = testing.DummyRequest()
         request.body = input_body.encode(request.charset)
@@ -114,9 +117,19 @@ class TestAggregatorAPI(object):
             assert response == output_value, "Response of PUT request must include the new configuration of the aggregator"
 
             for attribute in CONFIG_ATTRIBUTES:
-                assert self.config.get_settings().get(attribute) == output_value.get(attribute), "Aggregator settings configuration is not updated."
+                assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION][attribute] == str(output_value[attribute]), "Aggregator settings configuration is not updated."
+
+            assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Aggregator running status should not be updated after a configuration update."
+
+            # assert that the conf file is updated
+            updated_conf = configparser.ConfigParser()
+            conf_file = self.registry.get_settings().get(CONF_FILE_ATTRIBUTE)
+            assert updated_conf.read(conf_file) == [conf_file]
+            assert AGGREGATOR_CONFIG_SECTION in updated_conf.sections()
+
+            for attribute in CONFIG_ATTRIBUTES:
+                assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION][attribute] == updated_conf[AGGREGATOR_CONFIG_SECTION][attribute], "Aggregator settings configuration is not updated."
 
-            assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Aggregator running status should not be updated after a configuration update."
         else:
             error_raised = False
             try:
@@ -133,8 +146,8 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorController  # nested import so that importing the class view is part of the test itself
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
 
         request = testing.DummyRequest()
         input_body = '{"action": "start"}'
@@ -142,8 +155,8 @@ class TestAggregatorAPI(object):
 
         response = AggregatorController(request).put()
         assert response == {RUNNING_FLAG: True}, "The aggregator should have been started."
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been started."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is not None, "Aggregator process should have been initialized."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been started."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is not None, "Aggregator process should have been initialized."
 
         # kill the started process after the test is over
         pid = request.registry.settings[PROCESS_ATTRIBUTE].pid
@@ -156,16 +169,16 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorController  # nested import so that importing the class view is part of the test itself
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
 
         # send a start request to trigger the aggregator
         request = testing.DummyRequest()
         input_body = '{"action": "start"}'
         request.body = input_body.encode(request.charset)
         AggregatorController(request).put()
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is not None, "Aggregator process should have been initialized."
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Aggregator process should have been initialized."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is not None, "Aggregator process should have been initialized."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Aggregator process should have been initialized."
 
         # test stopping the aggregator process when it is running
         request = testing.DummyRequest()
@@ -174,8 +187,8 @@ class TestAggregatorAPI(object):
 
         response = AggregatorController(request).put()
         assert response == {RUNNING_FLAG: False}, "The aggregator should have been stopped."
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been stopped."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Aggregator process should have been terminated."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been stopped."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Aggregator process should have been terminated."
 
         sleep(2)  # put a 2 seconds timeout so that the aggregator process can terminate
 
@@ -186,8 +199,8 @@ class TestAggregatorAPI(object):
 
         response = AggregatorController(request).put()
         assert response == {RUNNING_FLAG: False}, "The aggregator should have been stopped."
-        assert  not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been stopped."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Aggregator process should have been terminated."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been stopped."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Aggregator process should have been terminated."
 
     def test_restart(self):
         """
@@ -196,8 +209,8 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorController  # nested import so that importing the class view is part of the test itself
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
 
         # test restarting the aggregator process when it is stopped
         request = testing.DummyRequest()
@@ -206,8 +219,8 @@ class TestAggregatorAPI(object):
 
         response = AggregatorController(request).put()
         assert response == {RUNNING_FLAG: True}, "The aggregator should have been restarted."
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been restarted."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE), "The aggregator process should have been reinitialised."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been restarted."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE), "The aggregator process should have been reinitialised."
 
         # test restarting the aggregator process when it is running
         request = testing.DummyRequest()
@@ -216,8 +229,8 @@ class TestAggregatorAPI(object):
 
         response = AggregatorController(request).put()
         assert response == {RUNNING_FLAG: True}, "The aggregator should have been restarted."
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been restarted."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE), "The aggregator process should have been reinitialised."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been restarted."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE), "The aggregator process should have been reinitialised."
 
         # kill the started process after the test is over
         pid = request.registry.settings[PROCESS_ATTRIBUTE].pid
@@ -239,8 +252,8 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorController  # nested import so that importing the class view is part of the test itself
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
 
         # test restarting the aggregator process when it is running
         request = testing.DummyRequest()
@@ -262,16 +275,16 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorController  # nested import so that importing the class view is part of the test itself
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
 
         request = testing.DummyRequest()
         response = AggregatorController(request).get()
 
         assert response == {'aggregator_running': False}, "Response must be a dictionary representing a JSON object with the correct status data of the aggregator."
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "A GET request must not modify the aggregator status flag."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "A GET request must not start the aggregator process."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "A GET request must not modify the aggregator status flag."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "A GET request must not start the aggregator process."
 
         # test status with malformed configuration
         # start the aggregator
@@ -279,7 +292,7 @@ class TestAggregatorAPI(object):
         input_body = '{"action": "start"}'
         request.body = input_body.encode(request.charset)
         AggregatorController(request).put()
-        self.config.get_settings()[MALFORMED_FLAG] = True
+        self.registry.get_settings()[MALFORMED_FLAG] = True
 
         request = testing.DummyRequest()
         response = AggregatorController(request).get()
@@ -289,9 +302,9 @@ class TestAggregatorAPI(object):
                             'comment': 'Aggregator is running in a malformed state - it uses an old version of the configuration. Please, restart it so that the updated configuration is used.'}, \
             "Response must be a dictionary representing a JSON object with the correct status data of the aggregator."
 
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "A GET request must not modify the aggregator status flag."
-        assert self.config.get_settings().get(MALFORMED_FLAG), "A GET request must not modify the aggregator malformed flag."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is not None, "A GET request must not stop the aggregator process."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "A GET request must not modify the aggregator status flag."
+        assert self.registry.get_settings().get(MALFORMED_FLAG), "A GET request must not modify the aggregator malformed flag."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is not None, "A GET request must not stop the aggregator process."
 
         # kill the started process after the test is over
         pid = request.registry.settings[PROCESS_ATTRIBUTE].pid
@@ -304,12 +317,12 @@ class TestAggregatorAPI(object):
 
         from clmcservice.views import AggregatorController, AggregatorConfig  # nested import so that importing the class view is part of the test itself
 
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
-        assert not self.config.get_settings().get(MALFORMED_FLAG), "Initially aggregator is not in a malformed state"
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
-        assert self.config.get_settings().get('aggregator_report_period') == 5, "Initial report period is 5 seconds."
-        assert self.config.get_settings().get('aggregator_database_name') == 'CLMCMetrics', "Initial database name the aggregator uses is CLMCMetrics."
-        assert self.config.get_settings().get('aggregator_database_url') == "http://172.40.231.51:8086", "Initial aggregator url is http://172.40.231.51:8086"
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "Initially aggregator is not running."
+        assert not self.registry.get_settings().get(MALFORMED_FLAG), "Initially aggregator is not in a malformed state"
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "Initially no aggregator process is running."
+        assert int(self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_report_period')) == 5, "Initial report period is 5 seconds."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_name') == 'CLMCMetrics', "Initial database name the aggregator uses is CLMCMetrics."
+        assert self.registry.get_settings()[CONF_OBJECT][AGGREGATOR_CONFIG_SECTION].get('aggregator_database_url') == "http://172.40.231.51:8086", "Initial aggregator url is http://172.40.231.51:8086"
 
         # start the aggregator with the default configuration
         request = testing.DummyRequest()
@@ -328,9 +341,9 @@ class TestAggregatorAPI(object):
         response = AggregatorConfig(request).put()
         assert response == output_body, "Response of PUT request must include the new configuration of the aggregator"
 
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator shouldn't be stopped when the configuration is updated."
-        assert self.config.get_settings().get(MALFORMED_FLAG), "The malformed flag should be set when the configuration is updated while the process is running."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is not None, "The aggregator shouldn't be stopped when the configuration is updated."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator shouldn't be stopped when the configuration is updated."
+        assert self.registry.get_settings().get(MALFORMED_FLAG), "The malformed flag should be set when the configuration is updated while the process is running."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is not None, "The aggregator shouldn't be stopped when the configuration is updated."
 
         # check that the malformed flag has been updated through a GET call
         request = testing.DummyRequest()
@@ -346,9 +359,9 @@ class TestAggregatorAPI(object):
         request.body = input_body.encode(request.charset)
         response = AggregatorController(request).put()
         assert response == {RUNNING_FLAG: True}, "The aggregator should have been restarted."
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been restarted."
-        assert not self.config.get_settings().get(MALFORMED_FLAG), "The malformed flag should have been reset to False."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is not None, "The aggregator should have been restarted."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been restarted."
+        assert not self.registry.get_settings().get(MALFORMED_FLAG), "The malformed flag should have been reset to False."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is not None, "The aggregator should have been restarted."
 
         # update the configuration again while the aggregator is running
         config_body = '{"aggregator_report_period": 30, "aggregator_database_name": "CLMCMetrics", "aggregator_database_url": "http://172.50.231.51:8086"}'
@@ -359,20 +372,67 @@ class TestAggregatorAPI(object):
         response = AggregatorConfig(request).put()
         assert response == output_body, "Response of PUT request must include the new configuration of the aggregator"
 
-        assert AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator shouldn't be stopped when the configuration is updated."
-        assert self.config.get_settings().get(MALFORMED_FLAG), "The malformed flag should be set when the configuration is updated while the process is running."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is not None, "The aggregator shouldn't be stopped when the configuration is updated."
+        assert AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator shouldn't be stopped when the configuration is updated."
+        assert self.registry.get_settings().get(MALFORMED_FLAG), "The malformed flag should be set when the configuration is updated while the process is running."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is not None, "The aggregator shouldn't be stopped when the configuration is updated."
 
         # stop the aggregator - this should also reset the malformed status flag
-        # restart the aggregator with the new configuration
         request = testing.DummyRequest()
         input_body = '{"action": "stop"}'
         request.body = input_body.encode(request.charset)
         response = AggregatorController(request).put()
         assert response == {RUNNING_FLAG: False}, "The aggregator should have been stopped."
-        assert not AggregatorController.is_process_running(self.config.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been stopped."
-        assert not self.config.get_settings().get(MALFORMED_FLAG), "The malformed flag should have been reset to False."
-        assert self.config.get_settings().get(PROCESS_ATTRIBUTE) is None, "The aggregator should have been stopped."
+        assert not AggregatorController.is_process_running(self.registry.get_settings().get(PROCESS_ATTRIBUTE)), "The aggregator should have been stopped."
+        assert not self.registry.get_settings().get(MALFORMED_FLAG), "The malformed flag should have been reset to False."
+        assert self.registry.get_settings().get(PROCESS_ATTRIBUTE) is None, "The aggregator should have been stopped."
+
+    def test_unconfigured_state(self):
+        """
+        Tests the behaviour of the service when in unconfigured state.
+        """
+
+        from clmcservice.views import AggregatorConfig, AggregatorController
+
+        self.registry.get_settings()[CONF_OBJECT] = None  # unconfigured state - conf object is None
+
+        # when doing a GET for the configuration we expect a bad request if the service is in unconfigured state
+        bad_request = False
+        bad_request_msg = None
+        try:
+            request = testing.DummyRequest()
+            AggregatorConfig(request).get()
+        except HTTPBadRequest as err:
+            bad_request = True
+            bad_request_msg = err.message
+
+        assert bad_request
+        assert bad_request_msg == "Aggregator has not been configured, yet. Send a PUT request to /aggregator/config with a JSON body of the configuration."
+
+        # when doing a PUT for the aggregator to start/stop/restart we expect a bad request if the service is in unconfigured state
+        for action in ('start', 'stop', 'restart'):
+            bad_request = False
+            bad_request_msg = None
+            try:
+                request = testing.DummyRequest()
+                request.body = ('{"action": "' + action + '"}').encode(request.charset)
+                AggregatorController(request).put()
+            except HTTPBadRequest as err:
+                bad_request = True
+                bad_request_msg = err.message
+
+            assert bad_request
+            assert bad_request_msg == "You must configure the aggregator before controlling it. Send a PUT request to /aggregator/config with a JSON body of the configuration."
+
+        # configure the aggregator
+        input_body = '{"aggregator_report_period": 10, "aggregator_database_name": "CLMCMetrics", "aggregator_database_url": "http://171.40.231.51:8086"}'
+        output_body = {'aggregator_report_period': 10, 'aggregator_database_name': "CLMCMetrics", 'aggregator_database_url': "http://171.40.231.51:8086"}
+        request = testing.DummyRequest()
+        request.body = input_body.encode(request.charset)
+        response = AggregatorConfig(request).put()
+        assert response == output_body
+
+        request = testing.DummyRequest()
+        assert AggregatorConfig(request).get() == output_body
 
 
 class TestRegexURL(object):
diff --git a/src/service/clmcservice/utilities.py b/src/service/clmcservice/utilities.py
index 44ccffed7ce4120b22c31b55a7b81cde33344f9b..14fd1b4114733f88f684bcf955de228961432b5e 100644
--- a/src/service/clmcservice/utilities.py
+++ b/src/service/clmcservice/utilities.py
@@ -1,3 +1,4 @@
+#!/usr/bin/python3
 """
 // © University of Southampton IT Innovation Centre, 2018
 //
@@ -23,7 +24,12 @@
 
 from json import loads
 from re import compile, IGNORECASE
+from configparser import ConfigParser
 
+CONF_FILE_ATTRIBUTE = 'configuration_file_path'  # the attribute pointing to the configuration file path
+CONF_OBJECT = 'configuration_object'  # the attribute, which stores the service configuration object
+
+AGGREGATOR_CONFIG_SECTION = "AGGREGATOR"  # the section in the configuration holding all the configuration attributes declared below
 CONFIG_ATTRIBUTES = ('aggregator_report_period', 'aggregator_database_name', 'aggregator_database_url')  # all of the configuration attributes - to be used as dictionary keys
 
 RUNNING_FLAG = 'aggregator_running'  # Attribute for storing the flag, which shows whether the aggregator is running or not - to be used as a dictionary key
@@ -121,6 +127,36 @@ def validate_round_trip_query_params(params):
     return params
 
 
+def validate_conf_file(conf_file_path):
+    """
+    Validates the aggregator's configuration file - checks for existence of the file path, whether it can be parsed as a configuration file and
+    whether it contains the required configuration attributes.
+
+    :param conf_file_path: the configuration file path to check
+
+    :return: the parsed configuration if valid, None otherwise
+    """
+
+    global AGGREGATOR_CONFIG_SECTION, CONFIG_ATTRIBUTES
+
+    conf = ConfigParser()
+    result = conf.read(conf_file_path)
+
+    # if result doesn't contain one element, namely the conf_file_path,
+    # then the configuration file cannot be parsed for some reason (doesn't exist, cannot be opened, invalid, etc.)
+    if len(result) == 0:
+        return None
+
+    if AGGREGATOR_CONFIG_SECTION not in conf.sections():
+        return None  # the config should include a section called AGGREGATOR
+
+    for key in CONFIG_ATTRIBUTES:
+        if key not in conf[AGGREGATOR_CONFIG_SECTION]:
+            return None  # the configuration must include each configuration attribute
+
+    return conf
+
+
 def generate_e2e_delay_report(path_id, source_sfr, target_sfr, endpoint, sf_instance, delay_forward, delay_reverse, delay_service, avg_request_size, avg_response_size, avg_bandwidth, time):
     """
     Generates a combined averaged measurement about the e2e delay and its contributing parts
diff --git a/src/service/clmcservice/views.py b/src/service/clmcservice/views.py
index b033675b2ff6123654e14fe01784ad4b0d64acc1..906c1c4e84a24ffaeb2e4536480e218ab28f2d64 100644
--- a/src/service/clmcservice/views.py
+++ b/src/service/clmcservice/views.py
@@ -1,3 +1,4 @@
+#!/usr/bin/python3
 """
 // © University of Southampton IT Innovation Centre, 2018
 //
@@ -27,11 +28,12 @@ from influxdb import InfluxDBClient
 from urllib.parse import urlparse
 from subprocess import Popen
 from clmcservice.utilities import validate_config_content, validate_action_content, validate_round_trip_query_params, \
-    CONFIG_ATTRIBUTES, ROUND_TRIP_ATTRIBUTES, RUNNING_FLAG, PROCESS_ATTRIBUTE, MALFORMED_FLAG, COMMENT_ATTRIBUTE, COMMENT_VALUE
+    CONF_OBJECT, CONF_FILE_ATTRIBUTE, AGGREGATOR_CONFIG_SECTION, CONFIG_ATTRIBUTES, ROUND_TRIP_ATTRIBUTES, RUNNING_FLAG, PROCESS_ATTRIBUTE, MALFORMED_FLAG, COMMENT_ATTRIBUTE, COMMENT_VALUE
 import os
 import os.path
 import sys
 import logging
+import configparser
 
 
 log = logging.getLogger('service_logger')
@@ -59,8 +61,12 @@ class AggregatorConfig(object):
         :return: A JSON response with the configuration of the aggregator.
         """
 
-        aggregator_data = self.request.registry.settings
-        config = {key: aggregator_data.get(key) for key in CONFIG_ATTRIBUTES}
+        aggregator_config_data = self.request.registry.settings[CONF_OBJECT]  # fetch the configuration object
+        if aggregator_config_data is None:
+            raise HTTPBadRequest("Aggregator has not been configured, yet. Send a PUT request to /aggregator/config with a JSON body of the configuration.")
+
+        config = {key: aggregator_config_data[AGGREGATOR_CONFIG_SECTION][key] for key in CONFIG_ATTRIBUTES}  # extract a json value containing the config attributes
+        config['aggregator_report_period'] = int(config['aggregator_report_period'])
 
         return config
 
@@ -72,27 +78,51 @@ class AggregatorConfig(object):
         :raises HTTPBadRequest: if request body is not a valid JSON for the configurator
         """
 
-        old_config = {attribute: self.request.registry.settings.get(attribute) for attribute in CONFIG_ATTRIBUTES}
-        new_config = self.request.body.decode(self.request.charset)
-
         try:
-            new_config = validate_config_content(new_config)
-
-            for attribute in CONFIG_ATTRIBUTES:
-                self.request.registry.settings[attribute] = new_config.get(attribute)
-
-            # if configuration is not already malformed, check whether the configuration is updated
-            if not self.request.registry.settings[MALFORMED_FLAG]:
-                malformed = old_config != new_config and AggregatorController.is_process_running(self.request.registry.settings.get(PROCESS_ATTRIBUTE))
-                self.request.registry.settings[MALFORMED_FLAG] = malformed
-                if malformed:
-                    new_config[MALFORMED_FLAG] = True
-                    new_config[COMMENT_ATTRIBUTE] = COMMENT_VALUE
+            new_config = self.request.body.decode(self.request.charset)
+            new_config = validate_config_content(new_config)  # validate the content and receive a json dictionary object
+        except AssertionError as e:
+            raise HTTPBadRequest("Bad request content. Configuration format is incorrect: {0}".format(e.args))
+
+        conf = self.request.registry.settings[CONF_OBJECT]
+        if conf is None:
+            conf = configparser.ConfigParser()
+            conf[AGGREGATOR_CONFIG_SECTION] = {}
+            self.request.registry.settings[CONF_OBJECT] = conf
+            old_config = {}
+        else:
+            # save the old configuration before updating so that it can be compared to the new one and checked for malformed state
+            old_config = {attribute: conf[AGGREGATOR_CONFIG_SECTION][attribute] for attribute in CONFIG_ATTRIBUTES}
+            old_config['aggregator_report_period'] = int(old_config['aggregator_report_period'])
+
+        for attribute in CONFIG_ATTRIBUTES:
+            conf[AGGREGATOR_CONFIG_SECTION][attribute] = str(new_config.get(attribute))  # update the configuration attributes
+
+        # if configuration is not already malformed, check whether the configuration is updated (changed in any way), if so (and the aggregator is running), malformed state is detected
+        if not self.request.registry.settings[MALFORMED_FLAG]:
+            malformed = old_config != new_config and AggregatorController.is_process_running(self.request.registry.settings.get(PROCESS_ATTRIBUTE))
+            self.request.registry.settings[MALFORMED_FLAG] = malformed
+            if malformed:
+                new_config[MALFORMED_FLAG] = True
+                new_config[COMMENT_ATTRIBUTE] = COMMENT_VALUE
+
+        self._write_conf_file()  # save the updated configuration to conf file
+        return new_config
+
+    def _write_conf_file(self):
+        """
+        Writes the configuration settings of the aggregator to a file with path stored at CONF_FILE_ATTRIBUTE
+        """
 
-            return new_config
+        conf = self.request.registry.settings[CONF_OBJECT]
+        conf_file_path = self.request.registry.settings[CONF_FILE_ATTRIBUTE]
+        os.makedirs(os.path.dirname(conf_file_path), exist_ok=True)
 
-        except AssertionError:
-            raise HTTPBadRequest("Bad request content - configuration format is incorrect.")
+        log.info("Saving configuration to file {0}.".format(conf_file_path))
+        with open(conf_file_path, 'w') as configfile:
+            log.info("Opened configuration file {0}.".format(conf_file_path))
+            conf.write(configfile)
+        log.info("Successfully saved configuration to file {0}.".format(conf_file_path))
 
 
 @view_defaults(route_name='aggregator_controller', renderer='json')
@@ -143,16 +173,22 @@ class AggregatorController(object):
         try:
             content = validate_action_content(content)
 
-            config = {attribute: self.request.registry.settings.get(attribute) for attribute in CONFIG_ATTRIBUTES}
+            conf = self.request.registry.settings[CONF_OBJECT]
+            if conf is None:
+                raise HTTPBadRequest("You must configure the aggregator before controlling it. Send a PUT request to /aggregator/config with a JSON body of the configuration.")
+
+            aggregator_config = {attribute: conf[AGGREGATOR_CONFIG_SECTION][attribute] for attribute in CONFIG_ATTRIBUTES}
+            aggregator_config['aggregator_report_period'] = int(aggregator_config['aggregator_report_period'])
 
             action = content['action']
 
             aggregator_running = self.is_process_running(self.request.registry.settings.get(PROCESS_ATTRIBUTE))
             if action == 'start':
                 if not aggregator_running:
-                    process = self.start_aggregator(config)
+                    process = self.start_aggregator(aggregator_config)
                     aggregator_running = True
                     self.request.registry.settings[PROCESS_ATTRIBUTE] = process
+                    self.request.registry.settings[MALFORMED_FLAG] = False
             elif action == 'stop':
                 self.stop_aggregator(self.request.registry.settings.get(PROCESS_ATTRIBUTE))
                 aggregator_running = False
@@ -160,7 +196,7 @@ class AggregatorController(object):
                 self.request.registry.settings[MALFORMED_FLAG] = False
             elif action == 'restart':
                 self.stop_aggregator(self.request.registry.settings.get(PROCESS_ATTRIBUTE))
-                process = self.start_aggregator(config)
+                process = self.start_aggregator(aggregator_config)
                 aggregator_running = True
                 self.request.registry.settings[PROCESS_ATTRIBUTE] = process
                 self.request.registry.settings[MALFORMED_FLAG] = False
@@ -245,13 +281,19 @@ class RoundTripTimeQuery(object):
 
         try:
             params = validate_round_trip_query_params(params)
-            config_data = {config_attribute: self.request.registry.settings.get(config_attribute) for config_attribute in CONFIG_ATTRIBUTES}
+
+            conf = self.request.registry.settings[CONF_OBJECT]
+            if conf is None:
+                raise HTTPBadRequest("You must configure the aggregator before making a round trip time query. Send a PUT request to /aggregator/config with a JSON body of the configuration.")
+
+            aggregator_config_data = {config_attribute: conf[AGGREGATOR_CONFIG_SECTION][config_attribute] for config_attribute in CONFIG_ATTRIBUTES}
+            aggregator_config_data['aggregator_report_period'] = int(aggregator_config_data['aggregator_report_period'])
 
             media_service = params.get(ROUND_TRIP_ATTRIBUTES[0])
             start_timestamp = params.get(ROUND_TRIP_ATTRIBUTES[1])
             end_timestamp = params.get(ROUND_TRIP_ATTRIBUTES[2])
-            influx_db_name = config_data.get(CONFIG_ATTRIBUTES[1])
-            influx_db_url = config_data.get(CONFIG_ATTRIBUTES[2])
+            influx_db_name = aggregator_config_data.get(CONFIG_ATTRIBUTES[1])
+            influx_db_url = aggregator_config_data.get(CONFIG_ATTRIBUTES[2])
 
             url_object = urlparse(influx_db_url)
             try:
diff --git a/src/service/development.ini b/src/service/development.ini
index 9345526c2834678369a156178b49e5bad39d6c56..390e2a57ed7e266dd5105fb1ef609b50e16f9207 100644
--- a/src/service/development.ini
+++ b/src/service/development.ini
@@ -14,10 +14,9 @@ pyramid.default_locale_name = en
 pyramid.includes = pyramid_debugtoolbar pyramid_exclog
 exclog.ignore =
 
-## Aggregator default configuration
-aggregator_report_period = 5
-aggregator_database_name = CLMCMetrics
-aggregator_database_url = http://172.40.231.51:8086
+## Configuration file path
+configuration_file_path = /etc/flame/clmc/service.conf
+
 
 # By default, the toolbar only appears for clients from IP addresses
 # '127.0.0.1' and '::1'.
diff --git a/src/service/production.ini b/src/service/production.ini
index 2e1cfcf66101b9eec64b22d8a71c2bd72b092961..3e8876b27dab359006c463c17ac24c12020ce171 100644
--- a/src/service/production.ini
+++ b/src/service/production.ini
@@ -14,10 +14,9 @@ pyramid.default_locale_name = en
 pyramid.includes = pyramid_exclog
 exclog.ignore =
 
-## Aggregator default configuration
-aggregator_report_period = 5
-aggregator_database_name = CLMCMetrics
-aggregator_database_url = http://172.40.231.51:8086
+## Configuration file path
+configuration_file_path = /etc/flame/clmc/service.conf
+
 
 ###
 # wsgi server configuration